pssht  latest
SSH server library written in PHP
Algorithms.php
1 <?php
2 
3 /*
4 * This file is part of pssht.
5 *
6 * (c) François Poirotte <clicky@erebot.net>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11 
12 namespace fpoirotte\Pssht;
13 
18 {
20  protected $algos;
21 
23  protected $savedAlgos;
24 
26  protected $interfaces;
27 
31  private function __construct()
32  {
33  $this->interfaces = array(
34  'MAC' => '\\fpoirotte\\Pssht\\MAC\\MACInterface',
35  'Compression' => '\\fpoirotte\\Pssht\\Compression\\CompressionInterface',
36  'Key' => '\\fpoirotte\\Pssht\\Key\\KeyInterface',
37  'KEX' => '\\fpoirotte\\Pssht\\KEX\\KEXInterface',
38  'Encryption' => '\\fpoirotte\\Pssht\\Encryption\\EncryptionInterface',
39  );
40 
41  $this->algos = array(
42  'MAC' => array(),
43  'Compression' => array(),
44  'Key' => array(),
45  'KEX' => array(),
46  'Encryption' => array(),
47  );
48 
49  $logging = \Plop\Plop::getInstance();
50  foreach (array_keys($this->algos) as $type) {
51  $it = new \RecursiveIteratorIterator(
52  new \RecursiveDirectoryIterator(
53  __DIR__ . DIRECTORY_SEPARATOR . $type,
54  \FilesystemIterator::UNIX_PATHS |
55  \FilesystemIterator::KEY_AS_PATHNAME |
56  \FilesystemIterator::CURRENT_AS_FILEINFO |
57  \FilesystemIterator::SKIP_DOTS
58  )
59  );
60  $dirLen = strlen(__DIR__ . DIRECTORY_SEPARATOR . $type);
61  foreach ($it as $entry) {
62  if (!$entry->isFile()) {
63  continue;
64  }
65 
66  if (substr($entry->getBasename(), -4) !== '.php') {
67  continue;
68  }
69 
70  $name = (string) substr($entry->getPathname(), $dirLen, -4);
71  $name = str_replace('/', '\\', $name);
72  $class = $this->getValidClass($type, $name);
73  if ($class === null) {
74  continue;
75  }
76 
77  $algo = $this->getValidAlgorithm($class);
78  if ($algo === null) {
79  continue;
80  }
81 
82  $logging->debug(
83  'Adding "%(algo)s" (from %(class)s) to supported %(type)s algorithms',
84  array('type' => $type, 'algo' => $algo, 'class' => $class)
85  );
86  $this->algos[$type][$algo] = $class;
87  }
88  uksort($this->algos[$type], array('self', 'sortAlgorithms'));
89  }
90  $this->savedAlgos = $this->algos;
91  }
92 
94  public function __clone()
95  {
96  throw new \RuntimeException();
97  }
98 
105  public static function factory()
106  {
107  static $instance = null;
108  if ($instance === null) {
109  $instance = new static();
110  }
111  return $instance;
112  }
113 
129  protected function getValidClass($type, $name)
130  {
131  $logging = \Plop\Plop::getInstance();
132  $w = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_1234567890\\';
133  if (!is_string($name) || strspn($name, $w) !== strlen($name)) {
134  $logging->debug(
135  'Skipping %(type)s algorithm "%(name)s" (invalid class name)',
136  array('type' => $type, 'name' => $name)
137  );
138  return null;
139  }
140 
141  // Non-existing classes.
142  $class = "\\fpoirotte\\Pssht\\$type$name";
143  if (!class_exists($class)) {
144  if (!interface_exists($class)) {
145  $logging->debug(
146  'Skipping %(type)s algorithm "%(name)s" (class does not exist)',
147  array('type' => $type, 'name' => $name)
148  );
149  }
150  return null;
151  }
152 
153  // Abstract classes.
154  $reflector = new \ReflectionClass($class);
155  if ($reflector->isAbstract()) {
156  return null;
157  }
158 
159  // Classes that implement AvailabilityInterface
160  // where the algorithm is not currently available.
161  $iface = '\\fpoirotte\\Pssht\\Algorithms\\AvailabilityInterface';
162  if ($reflector->implementsInterface($iface) && !$class::isAvailable()) {
163  $logging->debug(
164  'Skipping %(type)s algorithm "%(name)s" (not available)',
165  array('type' => $type, 'name' => $name)
166  );
167  return null;
168  }
169 
170  // Classes that do not implement the proper interface.
171  $iface = $this->interfaces[$type];
172  if ($iface !== null && !$reflector->implementsInterface($iface)) {
173  $logging->debug(
174  'Skipping %(type)s algorithm "%(name)s" (invalid interface)',
175  array('type' => $type, 'name' => $name)
176  );
177  return null;
178  }
179 
180  return $class;
181  }
182 
195  protected function getValidAlgorithm($class)
196  {
197  $logging = \Plop\Plop::getInstance();
198  $w = 'abcdefghijklmnopqrstuvwxyz' .
199  'ABCDEFGHIJKLMNOPQRSTUVWXYZ' .
200  '1234567890-@.';
201  $name = $class::getName();
202  if (!is_string($name) || strspn($name, $w) !== strlen($name)) {
203  $logging->debug(
204  'Skipping algorithm "%(name)s" (invalid algorithm name)',
205  array('name' => $name)
206  );
207  return null;
208  }
209  return $name;
210  }
211 
229  public function register($type, $class)
230  {
231  if (is_object($class)) {
232  $class = get_class($class);
233  }
234  if (!is_string($class) || !class_exists($class)) {
235  throw new \InvalidArgumentException();
236  }
237  if (!is_string($type) || !isset($this->algos[$type])) {
238  throw new \InvalidArgumentException();
239  }
240  $name = $this->getValidAlgorithm($class);
241  if ($name === null) {
242  throw new \InvalidArgumentException();
243  }
244 
245  $this->algos[$type][$name] = $class;
246  uksort($this->algos[$type], array('self', 'sortAlgorithms'));
247  return $this;
248  }
249 
262  public function unregister($type, $name)
263  {
264  if (!is_string($type) || !isset($this->algos[$type])) {
265  throw new \InvalidArgumentException();
266  }
267  if (!is_string($name)) {
268  throw new \InvalidArgumentException();
269  }
270  unset($this->algos[$type][$name]);
271  return $this;
272  }
273 
289  public function restore($type, $name)
290  {
291  if (!is_string($type) || !isset($this->algos[$type])) {
292  throw new \InvalidArgumentException();
293  }
294  if (isset($this->savedAlgos[$type][$name])) {
295  $this->algos[$type][$name] = $this->savedAlgos[$type][$name];
296  }
297  return $this;
298  }
299 
311  public function getAlgorithms($type)
312  {
313  if (!is_string($type) || !isset($this->algos[$type])) {
314  throw new \InvalidArgumentException();
315  }
316  return array_keys($this->algos[$type]);
317  }
318 
330  public function getClasses($type)
331  {
332  if (!is_string($type) || !isset($this->algos[$type])) {
333  throw new \InvalidArgumentException();
334  }
335  return $this->algos[$type];
336  }
337 
356  public function getClass($type, $name)
357  {
358  if (!is_string($type) || !isset($this->algos[$type])) {
359  throw new \InvalidArgumentException();
360  }
361  if (!is_string($name)) {
362  throw new \InvalidArgumentException();
363  }
364  if (!isset($this->algos[$type][$name])) {
365  return null;
366  }
367  return $this->algos[$type][$name];
368  }
369 
385  public static function sortAlgorithms($a, $b)
386  {
387  static $preferences = array(
388  // DH (KEX)
389  'curve25519-sha256@libssh.org',
390  'ecdh-sha2-nistp256',
391  'ecdh-sha2-nistp384',
392  'ecdh-sha2-nistp521',
393  'diffie-hellman-group-exchange-sha256',
394  'diffie-hellman-group-exchange-sha1',
395  'diffie-hellman-group14-sha1',
396  'diffie-hellman-group1-sha1',
397 
398  // Public Key
399  'ecdsa-sha2-nistp256-cert-v01@openssh.com',
400  'ecdsa-sha2-nistp384-cert-v01@openssh.com',
401  'ecdsa-sha2-nistp521-cert-v01@openssh.com',
402  'ecdsa-sha2-nistp256',
403  'ecdsa-sha2-nistp384',
404  'ecdsa-sha2-nistp521',
405  'ssh-ed25519-cert-v01@openssh.com',
406  'ssh-rsa-cert-v01@openssh.com',
407  'ssh-dss-cert-v01@openssh.com',
408  'ssh-rsa-cert-v00@openssh.com',
409  'ssh-dss-cert-v00@openssh.com',
410  'ssh-ed25519',
411  'ssh-rsa',
412  'ssh-dss',
413 
414  // Encryption
415  'aes128-ctr',
416  'aes192-ctr',
417  'aes256-ctr',
418  'aes128-gcm@openssh.com',
419  'aes256-gcm@openssh.com',
420  'chacha20-poly1305@openssh.com',
421  'arcfour256',
422  'arcfour128',
423  'aes128-cbc',
424  '3des-cbc',
425  'blowfish-cbc',
426  'cast128-cbc',
427  'aes192-cbc',
428  'aes256-cbc',
429  'arcfour',
430  'rijndael-cbc@lysator.liu.se',
431 
432  // MAC
433  'umac-64-etm@openssh.com',
434  'umac-128-etm@openssh.com',
435  'hmac-sha2-256-etm@openssh.com',
436  'hmac-sha2-512-etm@openssh.com',
437  'hmac-sha1-etm@openssh.com',
438  'umac-64@openssh.com',
439  'umac-128@openssh.com',
440  'hmac-sha2-256',
441  'hmac-sha2-512',
442  'hmac-sha1',
443  'hmac-md5-etm@openssh.com',
444  'hmac-ripemd160-etm@openssh.com',
445  'hmac-sha1-96-etm@openssh.com',
446  'hmac-md5-96-etm@openssh.com',
447  'hmac-md5',
448  'hmac-ripemd160',
449  'hmac-ripemd160@openssh.com',
450  'hmac-sha1-96',
451  'hmac-md5-96',
452 
453  // Compression
454  'none',
455  'zlib@openssh.com',
456  'zlib',
457  );
458 
459  $iA = array_search($a, $preferences, true);
460  $iB = array_search($b, $preferences, true);
461  if ($iA === false) {
462  return ($iB === false ? 0 : 1);
463  }
464  if ($iB === false) {
465  return -1;
466  }
467  return ($iA - $iB);
468  }
469 }
$algos
Array with currently available algorithms.
Definition: Algorithms.php:20
$interfaces
Mapping between algorithm types and their corresponding interface.
Definition: Algorithms.php:26
getValidClass($type, $name)
Definition: Algorithms.php:129
$savedAlgos
A backup of $algos when it was first populated.
Definition: Algorithms.php:23
__clone()
Prevent cloning of the singleton.
Definition: Algorithms.php:94
static sortAlgorithms($a, $b)
Definition: Algorithms.php:385