cryptal  latest
Cryptography Abstraction Layer
Registry.php
1 <?php
2 
3 namespace fpoirotte\Cryptal;
4 
15 
19 class Registry
20 {
46  private $metadata;
47 
48  static private $path = null;
49 
50  public function __construct()
51  {
52  if (null === self::$path) {
53  $registryPath = getenv('CRYPTAL_REGISTRY');
54  if (false === $registryPath || '' === $registryPath) {
55  self::$path = __DIR__ . DIRECTORY_SEPARATOR . 'registry.dat';
56  } else {
57  self::$path = $registryPath;
58  }
59  }
60 
61  $this->load(true);
62  }
63 
64  public static function getInstance()
65  {
66  static $instance = null;
67  if (null === $instance) {
68  $instance = new static;
69  }
70  return $instance;
71  }
72 
73  public function addCipher($packageName, $cls, CipherEnum $cipher, ModeEnum $mode, ImplementationTypeEnum $type)
74  {
75  $ifaces = class_implements($cls);
76  $iface = 'fpoirotte\\Cryptal\\Implementers\\CryptoInterface';
77  if (!$ifaces || !in_array($iface, $ifaces)) {
78  throw new \InvalidArgumentException("$cls does not implement $iface");
79  }
80  $this->metadata['crypt']["$cipher:$mode"][] = array($packageName, $cls, $type);
81  return $this;
82  }
83 
84  public function addHash($packageName, $cls, HashEnum $algo, ImplementationTypeEnum $type)
85  {
86  $ifaces = class_implements($cls);
87  $iface = 'fpoirotte\\Cryptal\\Implementers\\HashInterface';
88  if (!$ifaces || !in_array($iface, $ifaces)) {
89  throw new \InvalidArgumentException("$cls does not implement $iface");
90  }
91  $this->metadata['hash']["$algo"][] = array($packageName, $cls, $type);
92  return $this;
93  }
94 
95  public function addMac($packageName, $cls, MacEnum $algo, ImplementationTypeEnum $type)
96  {
97  $ifaces = class_implements($cls);
98  $iface = 'fpoirotte\\Cryptal\\Implementers\\MacInterface';
99  if (!$ifaces || !in_array($iface, $ifaces)) {
100  throw new \InvalidArgumentException("$cls does not implement $iface");
101  }
102  $this->metadata['mac']["$algo"][] = array($packageName, $cls, $type);
103  return $this;
104  }
105 
106  public function removeAlgorithms($packageName)
107  {
108  foreach ($this->metadata as &$algoTypes) {
109  foreach ($algoTypes as &$algos) {
110  foreach ($algos as $key => $desc) {
111  if ($desc[0] === $packageName) {
112  unset($algos[$key]);
113  }
114  }
115  }
116  }
117  return $this;
118  }
119 
120  public function load($registerDefaultAlgorithms = true)
121  {
122  $data = @file_get_contents(self::$path);
123  if (false === $data) {
124  $this->reset();
125  } else {
126  $this->metadata = unserialize($data);
127  $this->removeAlgorithms('');
128  }
129 
130  if ($registerDefaultAlgorithms) {
131  $this->registerDefaultAlgorithms();
132  }
133  return $this;
134  }
135 
136  public function registerDefaultAlgorithms()
137  {
138  // Ciphers
139  $this->addCipher(
140  '',
141  'fpoirotte\\Cryptal\\DefaultAlgorithms\\ChaCha20Openssh',
142  CipherEnum::CIPHER_CHACHA20_OPENSSH(),
143  ModeEnum::MODE_ECB(),
144  ImplementationTypeEnum::TYPE_USERLAND()
145  );
146  $this->addCipher(
147  '',
148  'fpoirotte\\Cryptal\\DefaultAlgorithms\\ChaCha20',
149  CipherEnum::CIPHER_CHACHA20(),
150  ModeEnum::MODE_ECB(),
151  ImplementationTypeEnum::TYPE_USERLAND()
152  );
153  $camellia = array(
154  CipherEnum::CIPHER_CAMELIA_128(),
155  CipherEnum::CIPHER_CAMELIA_192(),
156  CipherEnum::CIPHER_CAMELIA_256(),
157  );
158  foreach ($camellia as $cipher) {
159  $this->addCipher(
160  '',
161  'fpoirotte\\Cryptal\\DefaultAlgorithms\\Camellia',
162  $cipher,
163  ModeEnum::MODE_ECB(),
164  ImplementationTypeEnum::TYPE_USERLAND()
165  );
166  }
167 
168  // Hashes
169  $algos = array(
170  HashEnum::HASH_MD5(),
171  HashEnum::HASH_SHA1(),
172  );
173  foreach ($algos as $algo) {
174  $this->addHash(
175  '',
176  'fpoirotte\\Cryptal\\DefaultAlgorithms\\Hash',
177  $algo,
178  ImplementationTypeEnum::TYPE_COMPILED()
179  );
180  }
181 
182  // MACs
183  $this->addMac(
184  '',
185  'fpoirotte\\Cryptal\\DefaultAlgorithms\\Cmac',
186  MacEnum::MAC_CMAC(),
187  ImplementationTypeEnum::TYPE_USERLAND()
188  );
189  $this->addMac(
190  '',
191  'fpoirotte\\Cryptal\\DefaultAlgorithms\\Poly1305',
192  MacEnum::MAC_POLY1305(),
193  ImplementationTypeEnum::TYPE_USERLAND()
194  );
195  $algos = array(
196  MacEnum::MAC_UMAC_32(),
197  MacEnum::MAC_UMAC_64(),
198  MacEnum::MAC_UMAC_96(),
199  MacEnum::MAC_UMAC_128(),
200  );
201  foreach ($algos as $algo) {
202  $this->addMac(
203  '',
204  'fpoirotte\\Cryptal\\DefaultAlgorithms\\Umac',
205  $algo,
206  ImplementationTypeEnum::TYPE_USERLAND()
207  );
208  }
209 
210  return $this;
211  }
212 
213  public function save()
214  {
215  file_put_contents(self::$path, serialize($this->metadata));
216  return $this;
217  }
218 
219  public function reset()
220  {
221  $this->metadata = array(
222  'crypt' => array(),
223  'hash' => array(),
224  'mac' => array(),
225  );
226  return $this;
227  }
228 
229  protected static function findCipher(CipherEnum $cipher, ModeEnum $mode, $allowUnsafe)
230  {
231  $registry = self::getInstance();
232  $res = array(
233  (string) ImplementationTypeEnum::TYPE_ASSEMBLY() => null,
234  (string) ImplementationTypeEnum::TYPE_COMPILED() => null,
235  (string) ImplementationTypeEnum::TYPE_USERLAND() => null,
236  );
237 
238  if (empty($registry->metadata['crypt']["$cipher:$mode"])) {
239  throw new \Exception('Unsupported cipher/mode combination');
240  }
241 
242  foreach ($registry->metadata['crypt']["$cipher:$mode"] as $impl) {
243  $res["${impl[2]}"] = $impl[1];
244  }
245 
246  foreach ($res as $type => $cls) {
247  if (null !== $cls) {
248  if ($type == (string) ImplementationTypeEnum::TYPE_USERLAND() && !$allowUnsafe) {
249  throw new \Exception('No safe implementation found for cipher/mode');
250  }
251 
252  return $cls;
253  }
254  }
255 
256  throw new \Exception('Unsupported cipher/mode combination');
257  }
258 
259  public static function buildCipher(
260  CipherEnum $cipher,
261  ModeEnum $mode,
262  PaddingInterface $padding,
263  $key,
264  $tagLength = CryptoInterface::DEFAULT_TAG_LENGTH,
265  $allowUnsafe = false
266  ) {
267  if (!is_string($key)) {
268  throw new \InvalidArgumentException('Invalid key');
269  }
270 
271  if (!is_int($tagLength) || 0 > $tagLength) {
272  throw new \InvalidArgumentException('Invalid tag length');
273  }
274 
275  $cls = self::findCipher($cipher, $mode, $allowUnsafe);
276  return new $cls($cipher, $mode, $padding, $key, $tagLength);
277  }
278 
279  protected static function findHash(HashEnum $algo, $allowUnsafe)
280  {
281  $registry = self::getInstance();
282  $res = array(
283  (string) ImplementationTypeEnum::TYPE_ASSEMBLY() => null,
284  (string) ImplementationTypeEnum::TYPE_COMPILED() => null,
285  (string) ImplementationTypeEnum::TYPE_USERLAND() => null,
286  );
287 
288  if (empty($registry->metadata['hash']["$algo"])) {
289  throw new \Exception('Unsupported hash algorithm');
290  }
291 
292  foreach ($registry->metadata['hash']["$algo"] as $impl) {
293  $res["${impl[2]}"] = $impl[1];
294  }
295 
296  foreach ($res as $type => $cls) {
297  if (null !== $cls) {
298  if ($type == (string) ImplementationTypeEnum::TYPE_USERLAND() && !$allowUnsafe) {
299  throw new \Exception('No safe implementation found for hash');
300  }
301 
302  return $cls;
303  }
304  }
305 
306  throw new \Exception('Unsupported hash algorithm');
307  }
308 
309  public static function buildHash(HashEnum $algo, $allowUnsafe = false)
310  {
311  $cls = self::findHash($algo, $allowUnsafe);
312  return new $cls($algo);
313  }
314 
315  protected static function findMac(MacEnum $algo, $allowUnsafe)
316  {
317  $registry = self::getInstance();
318 
319  $res = array(
320  (string) ImplementationTypeEnum::TYPE_ASSEMBLY() => null,
321  (string) ImplementationTypeEnum::TYPE_COMPILED() => null,
322  (string) ImplementationTypeEnum::TYPE_USERLAND() => null,
323  );
324 
325  if (empty($registry->metadata['mac']["$algo"])) {
326  throw new \Exception('Unsupported MAC algorithm');
327  }
328 
329  foreach ($registry->metadata['mac']["$algo"] as $impl) {
330  $res["${impl[2]}"] = $impl[1];
331  }
332 
333  foreach ($res as $type => $cls) {
334  if (null !== $cls) {
335  if ($type == (string) ImplementationTypeEnum::TYPE_USERLAND() && !$allowUnsafe) {
336  throw new \Exception('No safe implementation found for MAC');
337  }
338 
339  return $cls;
340  }
341  }
342 
343  throw new \Exception('Unsupported MAC algorithm');
344  }
345 
346  public static function buildMac(
347  MacEnum $algo,
348  SubAlgorithmAbstractEnum $subAlgo,
349  $key,
350  $nonce = '',
351  $allowUnsafe = false
352  ) {
353  if (!is_string($key)) {
354  throw new \InvalidArgumentException('Invalid key');
355  }
356 
357  if (!is_string($nonce)) {
358  throw new \InvalidArgumentException('Invalid nonce');
359  }
360 
361  if ($subAlgo instanceof HashEnum) {
362  self::findHash($subAlgo, $allowUnsafe);
363  } elseif ($subAlgo instanceof CipherEnum) {
364  self::findCipher($subAlgo, ModeEnum::MODE_ECB(), $allowUnsafe);
365  } else {
366  throw new \InvalidArgumentException('Invalid inner algorithm');
367  }
368 
369  $cls = self::findMac($algo, $allowUnsafe);
370  return new $cls($algo, $subAlgo, $key, $nonce);
371  }
372 
373  public function getSupportedCiphers()
374  {
375  $res = array();
376  foreach ($this->metadata['crypt'] as $algo => $dummy) {
377  list($cipher, $mode) = explode(':', $algo);
378  $res[] = array(CipherEnum::$cipher(), ModeEnum::$mode());
379  }
380  return $res;
381  }
382 
383  public function getSupportedHashes()
384  {
385  $res = array();
386  foreach ($this->metadata['hash'] as $algo => $dummy) {
387  $res[] = HashEnum::$algo();
388  }
389  return $res;
390  }
391 
392  public function getSupportedMacs()
393  {
394  $res = array();
395  foreach ($this->metadata['mac'] as $algo => $dummy) {
396  $res[] = MacEnum::$algo();
397  }
398  return $res;
399  }
400 }