ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
XMLSecEnc.php
Go to the documentation of this file.
1 <?php
2 namespace RobRichards\XMLSecLibs;
3 
4 use DOMDocument;
5 use DOMNode;
6 use DOMXPath;
7 use Exception;
9 
50 class XMLSecEnc
51 {
52  const template = "<xenc:EncryptedData xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'>
53  <xenc:CipherData>
54  <xenc:CipherValue></xenc:CipherValue>
55  </xenc:CipherData>
56 </xenc:EncryptedData>";
57 
58  const Element = 'http://www.w3.org/2001/04/xmlenc#Element';
59  const Content = 'http://www.w3.org/2001/04/xmlenc#Content';
60  const URI = 3;
61  const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#';
62 
64  private $encdoc = null;
65 
67  private $rawNode = null;
68 
70  public $type = null;
71 
73  public $encKey = null;
74 
76  private $references = array();
77 
78  public function __construct()
79  {
80  $this->_resetTemplate();
81  }
82 
83  private function _resetTemplate()
84  {
85  $this->encdoc = new DOMDocument();
86  $this->encdoc->loadXML(self::template);
87  }
88 
95  public function addReference($name, $node, $type)
96  {
97  if (! $node instanceOf DOMNode) {
98  throw new Exception('$node is not of type DOMNode');
99  }
100  $curencdoc = $this->encdoc;
101  $this->_resetTemplate();
103  $this->encdoc = $curencdoc;
104  $refuri = XMLSecurityDSig::generateGUID();
105  $element = $encdoc->documentElement;
106  $element->setAttribute("Id", $refuri);
107  $this->references[$name] = array("node" => $node, "type" => $type, "encnode" => $encdoc, "refuri" => $refuri);
108  }
109 
113  public function setNode($node)
114  {
115  $this->rawNode = $node;
116  }
117 
127  public function encryptNode($objKey, $replace = true)
128  {
129  $data = '';
130  if (empty($this->rawNode)) {
131  throw new Exception('Node to encrypt has not been set');
132  }
133  if (! $objKey instanceof XMLSecurityKey) {
134  throw new Exception('Invalid Key');
135  }
136  $doc = $this->rawNode->ownerDocument;
137  $xPath = new DOMXPath($this->encdoc);
138  $objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue');
139  $cipherValue = $objList->item(0);
140  if ($cipherValue == null) {
141  throw new Exception('Error locating CipherValue element within template');
142  }
143  switch ($this->type) {
144  case (self::Element):
145  $data = $doc->saveXML($this->rawNode);
146  $this->encdoc->documentElement->setAttribute('Type', self::Element);
147  break;
148  case (self::Content):
149  $children = $this->rawNode->childNodes;
150  foreach ($children AS $child) {
151  $data .= $doc->saveXML($child);
152  }
153  $this->encdoc->documentElement->setAttribute('Type', self::Content);
154  break;
155  default:
156  throw new Exception('Type is currently not supported');
157  }
158 
159  $encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod'));
160  $encMethod->setAttribute('Algorithm', $objKey->getAlgorithm());
161  $cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode->parentNode->firstChild);
162 
163  $strEncrypt = base64_encode($objKey->encryptData($data));
164  $value = $this->encdoc->createTextNode($strEncrypt);
165  $cipherValue->appendChild($value);
166 
167  if ($replace) {
168  switch ($this->type) {
169  case (self::Element):
170  if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
171  return $this->encdoc;
172  }
173  $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true);
174  $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
175  return $importEnc;
176  case (self::Content):
177  $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true);
178  while ($this->rawNode->firstChild) {
179  $this->rawNode->removeChild($this->rawNode->firstChild);
180  }
181  $this->rawNode->appendChild($importEnc);
182  return $importEnc;
183  }
184  } else {
185  return $this->encdoc->documentElement;
186  }
187  }
188 
193  public function encryptReferences($objKey)
194  {
195  $curRawNode = $this->rawNode;
196  $curType = $this->type;
197  foreach ($this->references AS $name => $reference) {
198  $this->encdoc = $reference["encnode"];
199  $this->rawNode = $reference["node"];
200  $this->type = $reference["type"];
201  try {
202  $encNode = $this->encryptNode($objKey);
203  $this->references[$name]["encnode"] = $encNode;
204  } catch (Exception $e) {
205  $this->rawNode = $curRawNode;
206  $this->type = $curType;
207  throw $e;
208  }
209  }
210  $this->rawNode = $curRawNode;
211  $this->type = $curType;
212  }
213 
220  public function getCipherValue()
221  {
222  if (empty($this->rawNode)) {
223  throw new Exception('Node to decrypt has not been set');
224  }
225 
226  $doc = $this->rawNode->ownerDocument;
227  $xPath = new DOMXPath($doc);
228  $xPath->registerNamespace('xmlencr', self::XMLENCNS);
229  /* Only handles embedded content right now and not a reference */
230  $query = "./xmlencr:CipherData/xmlencr:CipherValue";
231  $nodeset = $xPath->query($query, $this->rawNode);
232  $node = $nodeset->item(0);
233 
234  if (!$node) {
235  return null;
236  }
237 
238  return base64_decode($node->nodeValue);
239  }
240 
254  public function decryptNode($objKey, $replace=true)
255  {
256  if (! $objKey instanceof XMLSecurityKey) {
257  throw new Exception('Invalid Key');
258  }
259 
260  $encryptedData = $this->getCipherValue();
261  if ($encryptedData) {
262  $decrypted = $objKey->decryptData($encryptedData);
263  if ($replace) {
264  switch ($this->type) {
265  case (self::Element):
266  $newdoc = new DOMDocument();
267  $newdoc->loadXML($decrypted);
268  if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
269  return $newdoc;
270  }
271  $importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, true);
272  $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
273  return $importEnc;
274  case (self::Content):
275  if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
276  $doc = $this->rawNode;
277  } else {
278  $doc = $this->rawNode->ownerDocument;
279  }
280  $newFrag = $doc->createDocumentFragment();
281  $newFrag->appendXML($decrypted);
282  $parent = $this->rawNode->parentNode;
283  $parent->replaceChild($newFrag, $this->rawNode);
284  return $parent;
285  default:
286  return $decrypted;
287  }
288  } else {
289  return $decrypted;
290  }
291  } else {
292  throw new Exception("Cannot locate encrypted data");
293  }
294  }
295 
304  public function encryptKey($srcKey, $rawKey, $append=true)
305  {
306  if ((! $srcKey instanceof XMLSecurityKey) || (! $rawKey instanceof XMLSecurityKey)) {
307  throw new Exception('Invalid Key');
308  }
309  $strEncKey = base64_encode($srcKey->encryptData($rawKey->key));
310  $root = $this->encdoc->documentElement;
311  $encKey = $this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptedKey');
312  if ($append) {
313  $keyInfo = $root->insertBefore($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'), $root->firstChild);
314  $keyInfo->appendChild($encKey);
315  } else {
316  $this->encKey = $encKey;
317  }
318  $encMethod = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod'));
319  $encMethod->setAttribute('Algorithm', $srcKey->getAlgorith());
320  if (! empty($srcKey->name)) {
321  $keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'));
322  $keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name));
323  }
324  $cipherData = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherData'));
325  $cipherData->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherValue', $strEncKey));
326  if (is_array($this->references) && count($this->references) > 0) {
327  $refList = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:ReferenceList'));
328  foreach ($this->references AS $name => $reference) {
329  $refuri = $reference["refuri"];
330  $dataRef = $refList->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:DataReference'));
331  $dataRef->setAttribute("URI", '#' . $refuri);
332  }
333  }
334  return;
335  }
336 
342  public function decryptKey($encKey)
343  {
344  if (! $encKey->isEncrypted) {
345  throw new Exception("Key is not Encrypted");
346  }
347  if (empty($encKey->key)) {
348  throw new Exception("Key is missing data to perform the decryption");
349  }
350  return $this->decryptNode($encKey, false);
351  }
352 
357  public function locateEncryptedData($element)
358  {
359  if ($element instanceof DOMDocument) {
360  $doc = $element;
361  } else {
362  $doc = $element->ownerDocument;
363  }
364  if ($doc) {
365  $xpath = new DOMXPath($doc);
366  $query = "//*[local-name()='EncryptedData' and namespace-uri()='".self::XMLENCNS."']";
367  $nodeset = $xpath->query($query);
368  return $nodeset->item(0);
369  }
370  return null;
371  }
372 
378  public function locateKey($node=null)
379  {
380  if (empty($node)) {
381  $node = $this->rawNode;
382  }
383  if (! $node instanceof DOMNode) {
384  return null;
385  }
386  if ($doc = $node->ownerDocument) {
387  $xpath = new DOMXPath($doc);
388  $xpath->registerNamespace('xmlsecenc', self::XMLENCNS);
389  $query = ".//xmlsecenc:EncryptionMethod";
390  $nodeset = $xpath->query($query, $node);
391  if ($encmeth = $nodeset->item(0)) {
392  $attrAlgorithm = $encmeth->getAttribute("Algorithm");
393  try {
394  $objKey = new XMLSecurityKey($attrAlgorithm, array('type' => 'private'));
395  } catch (Exception $e) {
396  return null;
397  }
398  return $objKey;
399  }
400  }
401  return null;
402  }
403 
410  public static function staticLocateKeyInfo($objBaseKey=null, $node=null)
411  {
412  if (empty($node) || (! $node instanceof DOMNode)) {
413  return null;
414  }
415  $doc = $node->ownerDocument;
416  if (!$doc) {
417  return null;
418  }
419 
420  $xpath = new DOMXPath($doc);
421  $xpath->registerNamespace('xmlsecenc', self::XMLENCNS);
422  $xpath->registerNamespace('xmlsecdsig', XMLSecurityDSig::XMLDSIGNS);
423  $query = "./xmlsecdsig:KeyInfo";
424  $nodeset = $xpath->query($query, $node);
425  $encmeth = $nodeset->item(0);
426  if (!$encmeth) {
427  /* No KeyInfo in EncryptedData / EncryptedKey. */
428  return $objBaseKey;
429  }
430 
431  foreach ($encmeth->childNodes AS $child) {
432  switch ($child->localName) {
433  case 'KeyName':
434  if (! empty($objBaseKey)) {
435  $objBaseKey->name = $child->nodeValue;
436  }
437  break;
438  case 'KeyValue':
439  foreach ($child->childNodes AS $keyval) {
440  switch ($keyval->localName) {
441  case 'DSAKeyValue':
442  throw new Exception("DSAKeyValue currently not supported");
443  case 'RSAKeyValue':
444  $modulus = null;
445  $exponent = null;
446  if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) {
447  $modulus = base64_decode($modulusNode->nodeValue);
448  }
449  if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) {
450  $exponent = base64_decode($exponentNode->nodeValue);
451  }
452  if (empty($modulus) || empty($exponent)) {
453  throw new Exception("Missing Modulus or Exponent");
454  }
455  $publicKey = XMLSecurityKey::convertRSA($modulus, $exponent);
456  $objBaseKey->loadKey($publicKey);
457  break;
458  }
459  }
460  break;
461  case 'RetrievalMethod':
462  $type = $child->getAttribute('Type');
463  if ($type !== 'http://www.w3.org/2001/04/xmlenc#EncryptedKey') {
464  /* Unsupported key type. */
465  break;
466  }
467  $uri = $child->getAttribute('URI');
468  if ($uri[0] !== '#') {
469  /* URI not a reference - unsupported. */
470  break;
471  }
472  $id = substr($uri, 1);
473 
474  $query = '//xmlsecenc:EncryptedKey[@Id="'.XPath::filterAttrValue($id, XPath::DOUBLE_QUOTE).'"]';
475  $keyElement = $xpath->query($query)->item(0);
476  if (!$keyElement) {
477  throw new Exception("Unable to locate EncryptedKey with @Id='$id'.");
478  }
479 
480  return XMLSecurityKey::fromEncryptedKeyElement($keyElement);
481  case 'EncryptedKey':
483  case 'X509Data':
484  if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) {
485  if ($x509certNodes->length > 0) {
486  $x509cert = $x509certNodes->item(0)->textContent;
487  $x509cert = str_replace(array("\r", "\n", " "), "", $x509cert);
488  $x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n";
489  $objBaseKey->loadKey($x509cert, false, true);
490  }
491  }
492  break;
493  }
494  }
495  return $objBaseKey;
496  }
497 
503  public function locateKeyInfo($objBaseKey=null, $node=null)
504  {
505  if (empty($node)) {
506  $node = $this->rawNode;
507  }
508  return self::staticLocateKeyInfo($objBaseKey, $node);
509  }
510 }
encryptNode($objKey, $replace=true)
Encrypt the selected node with the given key.
Definition: XMLSecEnc.php:127
static fromEncryptedKeyElement(DOMElement $element)
Create key from an EncryptedKey-element.
if(!array_key_exists('StateId', $_REQUEST)) $id
encryptKey($srcKey, $rawKey, $append=true)
Encrypt the XMLSecurityKey.
Definition: XMLSecEnc.php:304
static convertRSA($modulus, $exponent)
Hint: Modulus and Exponent must already be base64 decoded.
addReference($name, $node, $type)
Definition: XMLSecEnc.php:95
if($format !==null) $name
Definition: metadata.php:146
Load from Excel5 template
Definition: 30template.php:38
static staticLocateKeyInfo($objBaseKey=null, $node=null)
Definition: XMLSecEnc.php:410
getCipherValue()
Retrieve the CipherValue text from this encrypted node.
Definition: XMLSecEnc.php:220
$query
locateKeyInfo($objBaseKey=null, $node=null)
Definition: XMLSecEnc.php:503
Create styles array
The data for the language used.
locateKey($node=null)
Returns the key from the DOM.
Definition: XMLSecEnc.php:378
static generateGUID($prefix='pfx')
Generate guid.
decryptNode($objKey, $replace=true)
Decrypt this encrypted node.
Definition: XMLSecEnc.php:254