ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
XMLSecurityKey.php
Go to the documentation of this file.
1 <?php
2 namespace RobRichards\XMLSecLibs;
3 
4 use DOMElement;
5 use Exception;
6 
48 {
49  const TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
50  const AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
51  const AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
52  const AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
53  const RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
54  const RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
55  const DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1';
56  const RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
57  const RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
58  const RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
59  const RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
60  const HMAC_SHA1 = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1';
61 
63  private $cryptParams = array();
64 
66  public $type = 0;
67 
69  public $key = null;
70 
72  public $passphrase = "";
73 
75  public $iv = null;
76 
78  public $name = null;
79 
81  public $keyChain = null;
82 
84  public $isEncrypted = false;
85 
87  public $encryptedCtx = null;
88 
90  public $guid = null;
91 
97  private $x509Certificate = null;
98 
103  private $X509Thumbprint = null;
104 
110  public function __construct($type, $params=null)
111  {
112  switch ($type) {
113  case (self::TRIPLEDES_CBC):
114  $this->cryptParams['library'] = 'openssl';
115  $this->cryptParams['cipher'] = 'des-ede3-cbc';
116  $this->cryptParams['type'] = 'symmetric';
117  $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
118  $this->cryptParams['keysize'] = 24;
119  $this->cryptParams['blocksize'] = 8;
120  break;
121  case (self::AES128_CBC):
122  $this->cryptParams['library'] = 'openssl';
123  $this->cryptParams['cipher'] = 'aes-128-cbc';
124  $this->cryptParams['type'] = 'symmetric';
125  $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
126  $this->cryptParams['keysize'] = 16;
127  $this->cryptParams['blocksize'] = 16;
128  break;
129  case (self::AES192_CBC):
130  $this->cryptParams['library'] = 'openssl';
131  $this->cryptParams['cipher'] = 'aes-192-cbc';
132  $this->cryptParams['type'] = 'symmetric';
133  $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
134  $this->cryptParams['keysize'] = 24;
135  $this->cryptParams['blocksize'] = 16;
136  break;
137  case (self::AES256_CBC):
138  $this->cryptParams['library'] = 'openssl';
139  $this->cryptParams['cipher'] = 'aes-256-cbc';
140  $this->cryptParams['type'] = 'symmetric';
141  $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
142  $this->cryptParams['keysize'] = 32;
143  $this->cryptParams['blocksize'] = 16;
144  break;
145  case (self::RSA_1_5):
146  $this->cryptParams['library'] = 'openssl';
147  $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
148  $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
149  if (is_array($params) && ! empty($params['type'])) {
150  if ($params['type'] == 'public' || $params['type'] == 'private') {
151  $this->cryptParams['type'] = $params['type'];
152  break;
153  }
154  }
155  throw new Exception('Certificate "type" (private/public) must be passed via parameters');
156  case (self::RSA_OAEP_MGF1P):
157  $this->cryptParams['library'] = 'openssl';
158  $this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING;
159  $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
160  $this->cryptParams['hash'] = null;
161  if (is_array($params) && ! empty($params['type'])) {
162  if ($params['type'] == 'public' || $params['type'] == 'private') {
163  $this->cryptParams['type'] = $params['type'];
164  break;
165  }
166  }
167  throw new Exception('Certificate "type" (private/public) must be passed via parameters');
168  case (self::RSA_SHA1):
169  $this->cryptParams['library'] = 'openssl';
170  $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
171  $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
172  if (is_array($params) && ! empty($params['type'])) {
173  if ($params['type'] == 'public' || $params['type'] == 'private') {
174  $this->cryptParams['type'] = $params['type'];
175  break;
176  }
177  }
178  throw new Exception('Certificate "type" (private/public) must be passed via parameters');
179  case (self::RSA_SHA256):
180  $this->cryptParams['library'] = 'openssl';
181  $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
182  $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
183  $this->cryptParams['digest'] = 'SHA256';
184  if (is_array($params) && ! empty($params['type'])) {
185  if ($params['type'] == 'public' || $params['type'] == 'private') {
186  $this->cryptParams['type'] = $params['type'];
187  break;
188  }
189  }
190  throw new Exception('Certificate "type" (private/public) must be passed via parameters');
191  case (self::RSA_SHA384):
192  $this->cryptParams['library'] = 'openssl';
193  $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
194  $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
195  $this->cryptParams['digest'] = 'SHA384';
196  if (is_array($params) && ! empty($params['type'])) {
197  if ($params['type'] == 'public' || $params['type'] == 'private') {
198  $this->cryptParams['type'] = $params['type'];
199  break;
200  }
201  }
202  throw new Exception('Certificate "type" (private/public) must be passed via parameters');
203  case (self::RSA_SHA512):
204  $this->cryptParams['library'] = 'openssl';
205  $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
206  $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
207  $this->cryptParams['digest'] = 'SHA512';
208  if (is_array($params) && ! empty($params['type'])) {
209  if ($params['type'] == 'public' || $params['type'] == 'private') {
210  $this->cryptParams['type'] = $params['type'];
211  break;
212  }
213  }
214  throw new Exception('Certificate "type" (private/public) must be passed via parameters');
215  case (self::HMAC_SHA1):
216  $this->cryptParams['library'] = $type;
217  $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1';
218  break;
219  default:
220  throw new Exception('Invalid Key Type');
221  }
222  $this->type = $type;
223  }
224 
233  public function getSymmetricKeySize()
234  {
235  if (! isset($this->cryptParams['keysize'])) {
236  return null;
237  }
238  return $this->cryptParams['keysize'];
239  }
240 
247  public function generateSessionKey()
248  {
249  if (!isset($this->cryptParams['keysize'])) {
250  throw new Exception('Unknown key size for type "' . $this->type . '".');
251  }
252  $keysize = $this->cryptParams['keysize'];
253 
254  $key = openssl_random_pseudo_bytes($keysize);
255 
256  if ($this->type === self::TRIPLEDES_CBC) {
257  /* Make sure that the generated key has the proper parity bits set.
258  * Mcrypt doesn't care about the parity bits, but others may care.
259  */
260  for ($i = 0; $i < strlen($key); $i++) {
261  $byte = ord($key[$i]) & 0xfe;
262  $parity = 1;
263  for ($j = 1; $j < 8; $j++) {
264  $parity ^= ($byte >> $j) & 1;
265  }
266  $byte |= $parity;
267  $key[$i] = chr($byte);
268  }
269  }
270 
271  $this->key = $key;
272  return $key;
273  }
274 
281  public static function getRawThumbprint($cert)
282  {
283 
284  $arCert = explode("\n", $cert);
285  $data = '';
286  $inData = false;
287 
288  foreach ($arCert AS $curData) {
289  if (! $inData) {
290  if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
291  $inData = true;
292  }
293  } else {
294  if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
295  break;
296  }
297  $data .= trim($curData);
298  }
299  }
300 
301  if (! empty($data)) {
302  return strtolower(sha1(base64_decode($data)));
303  }
304 
305  return null;
306  }
307 
316  public function loadKey($key, $isFile=false, $isCert = false)
317  {
318  if ($isFile) {
319  $this->key = file_get_contents($key);
320  } else {
321  $this->key = $key;
322  }
323  if ($isCert) {
324  $this->key = openssl_x509_read($this->key);
325  openssl_x509_export($this->key, $str_cert);
326  $this->x509Certificate = $str_cert;
327  $this->key = $str_cert;
328  } else {
329  $this->x509Certificate = null;
330  }
331  if ($this->cryptParams['library'] == 'openssl') {
332  switch ($this->cryptParams['type']) {
333  case 'public':
334  if ($isCert) {
335  /* Load the thumbprint if this is an X509 certificate. */
336  $this->X509Thumbprint = self::getRawThumbprint($this->key);
337  }
338  $this->key = openssl_get_publickey($this->key);
339  if (! $this->key) {
340  throw new Exception('Unable to extract public key');
341  }
342  break;
343 
344  case 'private':
345  $this->key = openssl_get_privatekey($this->key, $this->passphrase);
346  break;
347 
348  case'symmetric':
349  if (strlen($this->key) < $this->cryptParams['keysize']) {
350  throw new Exception('Key must contain at least 25 characters for this cipher');
351  }
352  break;
353 
354  default:
355  throw new Exception('Unknown type');
356  }
357  }
358  }
359 
368  private function padISO10126($data, $blockSize)
369  {
370  if ($blockSize > 256) {
371  throw new Exception('Block size higher than 256 not allowed');
372  }
373  $padChr = $blockSize - (strlen($data) % $blockSize);
374  $pattern = chr($padChr);
375  return $data . str_repeat($pattern, $padChr);
376  }
377 
384  private function unpadISO10126($data)
385  {
386  $padChr = substr($data, -1);
387  $padLen = ord($padChr);
388  return substr($data, 0, -$padLen);
389  }
390 
397  private function encryptSymmetric($data)
398  {
399  $this->iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->cryptParams['cipher']));
400  $data = $this->padISO10126($data, $this->cryptParams['blocksize']);
401  $encrypted = openssl_encrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv);
402  if (false === $encrypted) {
403  throw new Exception('Failure encrypting Data (openssl symmetric) - ' . openssl_error_string());
404  }
405  return $this->iv . $encrypted;
406  }
407 
414  private function decryptSymmetric($data)
415  {
416  $iv_length = openssl_cipher_iv_length($this->cryptParams['cipher']);
417  $this->iv = substr($data, 0, $iv_length);
418  $data = substr($data, $iv_length);
419  $decrypted = openssl_decrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv);
420  if (false === $decrypted) {
421  throw new Exception('Failure decrypting Data (openssl symmetric) - ' . openssl_error_string());
422  }
423  return $this->unpadISO10126($decrypted);
424  }
425 
433  private function encryptPublic($data)
434  {
435  if (! openssl_public_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) {
436  throw new Exception('Failure encrypting Data (openssl public) - ' . openssl_error_string());
437  }
438  return $encrypted;
439  }
440 
448  private function decryptPublic($data)
449  {
450  if (! openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
451  throw new Exception('Failure decrypting Data (openssl public) - ' . openssl_error_string());
452  }
453  return $decrypted;
454  }
455 
463  private function encryptPrivate($data)
464  {
465  if (! openssl_private_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) {
466  throw new Exception('Failure encrypting Data (openssl private) - ' . openssl_error_string());
467  }
468  return $encrypted;
469  }
470 
478  private function decryptPrivate($data)
479  {
480  if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
481  throw new Exception('Failure decrypting Data (openssl private) - ' . openssl_error_string());
482  }
483  return $decrypted;
484  }
485 
493  private function signOpenSSL($data)
494  {
495  $algo = OPENSSL_ALGO_SHA1;
496  if (! empty($this->cryptParams['digest'])) {
497  $algo = $this->cryptParams['digest'];
498  }
499  if (! openssl_sign($data, $signature, $this->key, $algo)) {
500  throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo);
501  }
502  return $signature;
503  }
504 
521  private function verifyOpenSSL($data, $signature)
522  {
523  $algo = OPENSSL_ALGO_SHA1;
524  if (! empty($this->cryptParams['digest'])) {
525  $algo = $this->cryptParams['digest'];
526  }
527  return openssl_verify($data, $signature, $this->key, $algo);
528  }
529 
536  public function encryptData($data)
537  {
538  if ($this->cryptParams['library'] === 'openssl') {
539  switch ($this->cryptParams['type']) {
540  case 'symmetric':
541  return $this->encryptSymmetric($data);
542  case 'public':
543  return $this->encryptPublic($data);
544  case 'private':
545  return $this->encryptPrivate($data);
546  }
547  }
548  }
549 
556  public function decryptData($data)
557  {
558  if ($this->cryptParams['library'] === 'openssl') {
559  switch ($this->cryptParams['type']) {
560  case 'symmetric':
561  return $this->decryptSymmetric($data);
562  case 'public':
563  return $this->decryptPublic($data);
564  case 'private':
565  return $this->decryptPrivate($data);
566  }
567  }
568  }
569 
576  public function signData($data)
577  {
578  switch ($this->cryptParams['library']) {
579  case 'openssl':
580  return $this->signOpenSSL($data);
581  case (self::HMAC_SHA1):
582  return hash_hmac("sha1", $data, $this->key, true);
583  }
584  }
585 
602  public function verifySignature($data, $signature)
603  {
604  switch ($this->cryptParams['library']) {
605  case 'openssl':
606  return $this->verifyOpenSSL($data, $signature);
607  case (self::HMAC_SHA1):
608  $expectedSignature = hash_hmac("sha1", $data, $this->key, true);
609  return strcmp($signature, $expectedSignature) == 0;
610  }
611  }
612 
618  public function getAlgorith()
619  {
620  return $this->getAlgorithm();
621  }
622 
626  public function getAlgorithm()
627  {
628  return $this->cryptParams['method'];
629  }
630 
637  public static function makeAsnSegment($type, $string)
638  {
639  switch ($type) {
640  case 0x02:
641  if (ord($string) > 0x7f)
642  $string = chr(0).$string;
643  break;
644  case 0x03:
645  $string = chr(0).$string;
646  break;
647  }
648 
649  $length = strlen($string);
650 
651  if ($length < 128) {
652  $output = sprintf("%c%c%s", $type, $length, $string);
653  } else if ($length < 0x0100) {
654  $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string);
655  } else if ($length < 0x010000) {
656  $output = sprintf("%c%c%c%c%s", $type, 0x82, $length / 0x0100, $length % 0x0100, $string);
657  } else {
658  $output = null;
659  }
660  return $output;
661  }
662 
670  public static function convertRSA($modulus, $exponent)
671  {
672  /* make an ASN publicKeyInfo */
673  $exponentEncoding = self::makeAsnSegment(0x02, $exponent);
674  $modulusEncoding = self::makeAsnSegment(0x02, $modulus);
675  $sequenceEncoding = self::makeAsnSegment(0x30, $modulusEncoding.$exponentEncoding);
676  $bitstringEncoding = self::makeAsnSegment(0x03, $sequenceEncoding);
677  $rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500");
678  $publicKeyInfo = self::makeAsnSegment(0x30, $rsaAlgorithmIdentifier.$bitstringEncoding);
679 
680  /* encode the publicKeyInfo in base64 and add PEM brackets */
681  $publicKeyInfoBase64 = base64_encode($publicKeyInfo);
682  $encoding = "-----BEGIN PUBLIC KEY-----\n";
683  $offset = 0;
684  while ($segment = substr($publicKeyInfoBase64, $offset, 64)) {
685  $encoding = $encoding.$segment."\n";
686  $offset += 64;
687  }
688  return $encoding."-----END PUBLIC KEY-----\n";
689  }
690 
694  public function serializeKey($parent)
695  {
696 
697  }
698 
707  public function getX509Certificate()
708  {
709  return $this->x509Certificate;
710  }
711 
721  public function getX509Thumbprint()
722  {
723  return $this->X509Thumbprint;
724  }
725 
726 
735  public static function fromEncryptedKeyElement(DOMElement $element)
736  {
737 
738  $objenc = new XMLSecEnc();
739  $objenc->setNode($element);
740  if (! $objKey = $objenc->locateKey()) {
741  throw new Exception("Unable to locate algorithm for this Encrypted Key");
742  }
743  $objKey->isEncrypted = true;
744  $objKey->encryptedCtx = $objenc;
745  XMLSecEnc::staticLocateKeyInfo($objKey, $element);
746  return $objKey;
747  }
748 
749 }
getSymmetricKeySize()
Retrieve the key size for the symmetric encryption algorithm.
static getRawThumbprint($cert)
Get the raw thumbprint of a certificate.
static fromEncryptedKeyElement(DOMElement $element)
Create key from an EncryptedKey-element.
signOpenSSL($data)
Signs the given data (string) using the openssl-extension.
encryptPublic($data)
Encrypts the given public data (string) using the openssl-extension.
signData($data)
Signs the data (string) using the extension assigned to the type in the constructor.
$algo
Definition: pwgen.php:34
encryptSymmetric($data)
Encrypts the given data (string) using the openssl-extension.
static convertRSA($modulus, $exponent)
Hint: Modulus and Exponent must already be base64 decoded.
encryptPrivate($data)
Encrypts the given private data (string) using the openssl-extension.
decryptSymmetric($data)
Decrypts the given data (string) using the openssl-extension.
loadKey($key, $isFile=false, $isCert=false)
Loads the given key, or - with isFile set true - the key from the keyfile.
verifyOpenSSL($data, $signature)
Verifies the given data (string) belonging to the given signature using the openssl-extension.
static staticLocateKeyInfo($objBaseKey=null, $node=null)
Definition: XMLSecEnc.php:410
unpadISO10126($data)
Remove ISO 10126 Padding.
verifySignature($data, $signature)
Verifies the data (string) against the given signature using the extension assigned to the type in th...
getX509Thumbprint()
Get the thumbprint of this X509 certificate.
generateSessionKey()
Generates a session key using the openssl-extension.
$i
Definition: disco.tpl.php:19
encryptData($data)
Encrypts the given data (string) using the regarding php-extension, depending on the library assigned...
decryptPublic($data)
Decrypts the given public data (string) using the openssl-extension.
padISO10126($data, $blockSize)
ISO 10126 Padding.
decryptPrivate($data)
Decrypts the given private data (string) using the openssl-extension.
$data
Definition: bench.php:6
getX509Certificate()
Retrieve the X509 certificate this key represents.
decryptData($data)
Decrypts the given data (string) using the regarding php-extension, depending on the library assigned...