ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
XMLSecurityDSig.php
Go to the documentation of this file.
1 <?php
2 namespace RobRichards\XMLSecLibs;
3 
4 use DOMDocument;
5 use DOMElement;
6 use DOMNode;
7 use DOMXPath;
8 use Exception;
10 
52 {
53  const XMLDSIGNS = 'http://www.w3.org/2000/09/xmldsig#';
54  const SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1';
55  const SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256';
56  const SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#sha384';
57  const SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512';
58  const RIPEMD160 = 'http://www.w3.org/2001/04/xmlenc#ripemd160';
59 
60  const C14N = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
61  const C14N_COMMENTS = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments';
62  const EXC_C14N = 'http://www.w3.org/2001/10/xml-exc-c14n#';
63  const EXC_C14N_COMMENTS = 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments';
64 
65  const template = '<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
66  <ds:SignedInfo>
67  <ds:SignatureMethod />
68  </ds:SignedInfo>
69 </ds:Signature>';
70 
71  const BASE_TEMPLATE = '<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
72  <SignedInfo>
73  <SignatureMethod />
74  </SignedInfo>
75 </Signature>';
76 
78  public $sigNode = null;
79 
81  public $idKeys = array();
82 
84  public $idNS = array();
85 
87  private $signedInfo = null;
88 
90  private $xPathCtx = null;
91 
93  private $canonicalMethod = null;
94 
96  private $prefix = '';
97 
99  private $searchpfx = 'secdsig';
100 
105  private $validatedNodes = null;
106 
110  public function __construct($prefix='ds')
111  {
112  $template = self::BASE_TEMPLATE;
113  if (! empty($prefix)) {
114  $this->prefix = $prefix.':';
115  $search = array("<S", "</S", "xmlns=");
116  $replace = array("<$prefix:S", "</$prefix:S", "xmlns:$prefix=");
117  $template = str_replace($search, $replace, $template);
118  }
119  $sigdoc = new DOMDocument();
120  $sigdoc->loadXML($template);
121  $this->sigNode = $sigdoc->documentElement;
122  }
123 
127  private function resetXPathObj()
128  {
129  $this->xPathCtx = null;
130  }
131 
137  private function getXPathObj()
138  {
139  if (empty($this->xPathCtx) && ! empty($this->sigNode)) {
140  $xpath = new DOMXPath($this->sigNode->ownerDocument);
141  $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
142  $this->xPathCtx = $xpath;
143  }
144  return $this->xPathCtx;
145  }
146 
154  public static function generateGUID($prefix='pfx')
155  {
156  $uuid = md5(uniqid(mt_rand(), true));
157  $guid = $prefix.substr($uuid, 0, 8)."-".
158  substr($uuid, 8, 4)."-".
159  substr($uuid, 12, 4)."-".
160  substr($uuid, 16, 4)."-".
161  substr($uuid, 20, 12);
162  return $guid;
163  }
164 
174  public static function generate_GUID($prefix='pfx')
175  {
176  return self::generateGUID($prefix);
177  }
178 
184  public function locateSignature($objDoc, $pos=0)
185  {
186  if ($objDoc instanceof DOMDocument) {
187  $doc = $objDoc;
188  } else {
189  $doc = $objDoc->ownerDocument;
190  }
191  if ($doc) {
192  $xpath = new DOMXPath($doc);
193  $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
194  $query = ".//secdsig:Signature";
195  $nodeset = $xpath->query($query, $objDoc);
196  $this->sigNode = $nodeset->item($pos);
197  $query = "./secdsig:SignedInfo";
198  $nodeset = $xpath->query($query, $this->sigNode);
199  if ($nodeset->length > 1) {
200  throw new Exception("Invalid structure - Too many SignedInfo elements found");
201  }
202  return $this->sigNode;
203  }
204  return null;
205  }
206 
212  public function createNewSignNode($name, $value=null)
213  {
214  $doc = $this->sigNode->ownerDocument;
215  if (! is_null($value)) {
216  $node = $doc->createElementNS(self::XMLDSIGNS, $this->prefix.$name, $value);
217  } else {
218  $node = $doc->createElementNS(self::XMLDSIGNS, $this->prefix.$name);
219  }
220  return $node;
221  }
222 
227  public function setCanonicalMethod($method)
228  {
229  switch ($method) {
230  case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
231  case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
232  case 'http://www.w3.org/2001/10/xml-exc-c14n#':
233  case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
234  $this->canonicalMethod = $method;
235  break;
236  default:
237  throw new Exception('Invalid Canonical Method');
238  }
239  if ($xpath = $this->getXPathObj()) {
240  $query = './'.$this->searchpfx.':SignedInfo';
241  $nodeset = $xpath->query($query, $this->sigNode);
242  if ($sinfo = $nodeset->item(0)) {
243  $query = './'.$this->searchpfx.'CanonicalizationMethod';
244  $nodeset = $xpath->query($query, $sinfo);
245  if (! ($canonNode = $nodeset->item(0))) {
246  $canonNode = $this->createNewSignNode('CanonicalizationMethod');
247  $sinfo->insertBefore($canonNode, $sinfo->firstChild);
248  }
249  $canonNode->setAttribute('Algorithm', $this->canonicalMethod);
250  }
251  }
252  }
253 
261  private function canonicalizeData($node, $canonicalmethod, $arXPath=null, $prefixList=null)
262  {
263  $exclusive = false;
264  $withComments = false;
265  switch ($canonicalmethod) {
266  case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
267  $exclusive = false;
268  $withComments = false;
269  break;
270  case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
271  $withComments = true;
272  break;
273  case 'http://www.w3.org/2001/10/xml-exc-c14n#':
274  $exclusive = true;
275  break;
276  case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
277  $exclusive = true;
278  $withComments = true;
279  break;
280  }
281 
282  if (is_null($arXPath) && ($node instanceof DOMNode) && ($node->ownerDocument !== null) && $node->isSameNode($node->ownerDocument->documentElement)) {
283  /* Check for any PI or comments as they would have been excluded */
284  $element = $node;
285  while ($refnode = $element->previousSibling) {
286  if ($refnode->nodeType == XML_PI_NODE || (($refnode->nodeType == XML_COMMENT_NODE) && $withComments)) {
287  break;
288  }
289  $element = $refnode;
290  }
291  if ($refnode == null) {
292  $node = $node->ownerDocument;
293  }
294  }
295 
296  return $node->C14N($exclusive, $withComments, $arXPath, $prefixList);
297  }
298 
302  public function canonicalizeSignedInfo()
303  {
304 
305  $doc = $this->sigNode->ownerDocument;
306  $canonicalmethod = null;
307  if ($doc) {
308  $xpath = $this->getXPathObj();
309  $query = "./secdsig:SignedInfo";
310  $nodeset = $xpath->query($query, $this->sigNode);
311  if ($nodeset->length > 1) {
312  throw new Exception("Invalid structure - Too many SignedInfo elements found");
313  }
314  if ($signInfoNode = $nodeset->item(0)) {
315  $query = "./secdsig:CanonicalizationMethod";
316  $nodeset = $xpath->query($query, $signInfoNode);
317  if ($canonNode = $nodeset->item(0)) {
318  $canonicalmethod = $canonNode->getAttribute('Algorithm');
319  }
320  $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod);
321  return $this->signedInfo;
322  }
323  }
324  return null;
325  }
326 
334  public function calculateDigest($digestAlgorithm, $data, $encode = true)
335  {
336  switch ($digestAlgorithm) {
337  case self::SHA1:
338  $alg = 'sha1';
339  break;
340  case self::SHA256:
341  $alg = 'sha256';
342  break;
343  case self::SHA384:
344  $alg = 'sha384';
345  break;
346  case self::SHA512:
347  $alg = 'sha512';
348  break;
349  case self::RIPEMD160:
350  $alg = 'ripemd160';
351  break;
352  default:
353  throw new Exception("Cannot validate digest: Unsupported Algorithm <$digestAlgorithm>");
354  }
355 
356  $digest = hash($alg, $data, true);
357  if ($encode) {
358  $digest = base64_encode($digest);
359  }
360  return $digest;
361 
362  }
363 
369  public function validateDigest($refNode, $data)
370  {
371  $xpath = new DOMXPath($refNode->ownerDocument);
372  $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
373  $query = 'string(./secdsig:DigestMethod/@Algorithm)';
374  $digestAlgorithm = $xpath->evaluate($query, $refNode);
375  $digValue = $this->calculateDigest($digestAlgorithm, $data, false);
376  $query = 'string(./secdsig:DigestValue)';
377  $digestValue = $xpath->evaluate($query, $refNode);
378  return ($digValue === base64_decode($digestValue));
379  }
380 
387  public function processTransforms($refNode, $objData, $includeCommentNodes = true)
388  {
389  $data = $objData;
390  $xpath = new DOMXPath($refNode->ownerDocument);
391  $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
392  $query = './secdsig:Transforms/secdsig:Transform';
393  $nodelist = $xpath->query($query, $refNode);
394  $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
395  $arXPath = null;
396  $prefixList = null;
397  foreach ($nodelist AS $transform) {
398  $algorithm = $transform->getAttribute("Algorithm");
399  switch ($algorithm) {
400  case 'http://www.w3.org/2001/10/xml-exc-c14n#':
401  case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
402 
403  if (!$includeCommentNodes) {
404  /* We remove comment nodes by forcing it to use a canonicalization
405  * without comments.
406  */
407  $canonicalMethod = 'http://www.w3.org/2001/10/xml-exc-c14n#';
408  } else {
409  $canonicalMethod = $algorithm;
410  }
411 
412  $node = $transform->firstChild;
413  while ($node) {
414  if ($node->localName == 'InclusiveNamespaces') {
415  if ($pfx = $node->getAttribute('PrefixList')) {
416  $arpfx = array();
417  $pfxlist = explode(" ", $pfx);
418  foreach ($pfxlist AS $pfx) {
419  $val = trim($pfx);
420  if (! empty($val)) {
421  $arpfx[] = $val;
422  }
423  }
424  if (count($arpfx) > 0) {
425  $prefixList = $arpfx;
426  }
427  }
428  break;
429  }
430  $node = $node->nextSibling;
431  }
432  break;
433  case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
434  case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
435  if (!$includeCommentNodes) {
436  /* We remove comment nodes by forcing it to use a canonicalization
437  * without comments.
438  */
439  $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
440  } else {
441  $canonicalMethod = $algorithm;
442  }
443 
444  break;
445  case 'http://www.w3.org/TR/1999/REC-xpath-19991116':
446  $node = $transform->firstChild;
447  while ($node) {
448  if ($node->localName == 'XPath') {
449  $arXPath = array();
450  $arXPath['query'] = '(.//. | .//@* | .//namespace::*)['.$node->nodeValue.']';
451  $arXPath['namespaces'] = array();
452  $nslist = $xpath->query('./namespace::*', $node);
453  foreach ($nslist AS $nsnode) {
454  if ($nsnode->localName != "xml") {
455  $arXPath['namespaces'][$nsnode->localName] = $nsnode->nodeValue;
456  }
457  }
458  break;
459  }
460  $node = $node->nextSibling;
461  }
462  break;
463  }
464  }
465  if ($data instanceof DOMNode) {
466  $data = $this->canonicalizeData($objData, $canonicalMethod, $arXPath, $prefixList);
467  }
468  return $data;
469  }
470 
475  public function processRefNode($refNode)
476  {
477  $dataObject = null;
478 
479  /*
480  * Depending on the URI, we may not want to include comments in the result
481  * See: http://www.w3.org/TR/xmldsig-core/#sec-ReferenceProcessingModel
482  */
483  $includeCommentNodes = true;
484 
485  if ($uri = $refNode->getAttribute("URI")) {
486  $arUrl = parse_url($uri);
487  if (empty($arUrl['path'])) {
488  if ($identifier = $arUrl['fragment']) {
489 
490  /* This reference identifies a node with the given id by using
491  * a URI on the form "#identifier". This should not include comments.
492  */
493  $includeCommentNodes = false;
494 
495  $xPath = new DOMXPath($refNode->ownerDocument);
496  if ($this->idNS && is_array($this->idNS)) {
497  foreach ($this->idNS as $nspf => $ns) {
498  $xPath->registerNamespace($nspf, $ns);
499  }
500  }
501  $iDlist = '@Id="'.XPath::filterAttrValue($identifier, XPath::DOUBLE_QUOTE).'"';
502  if (is_array($this->idKeys)) {
503  foreach ($this->idKeys as $idKey) {
504  $iDlist .= " or @".XPath::filterAttrName($idKey).'="'.
505  XPath::filterAttrValue($identifier, XPath::DOUBLE_QUOTE).'"';
506  }
507  }
508  $query = '//*['.$iDlist.']';
509  $dataObject = $xPath->query($query)->item(0);
510  } else {
511  $dataObject = $refNode->ownerDocument;
512  }
513  }
514  } else {
515  /* This reference identifies the root node with an empty URI. This should
516  * not include comments.
517  */
518  $includeCommentNodes = false;
519 
520  $dataObject = $refNode->ownerDocument;
521  }
522  $data = $this->processTransforms($refNode, $dataObject, $includeCommentNodes);
523  if (!$this->validateDigest($refNode, $data)) {
524  return false;
525  }
526 
527  if ($dataObject instanceof DOMNode) {
528  /* Add this node to the list of validated nodes. */
529  if (! empty($identifier)) {
530  $this->validatedNodes[$identifier] = $dataObject;
531  } else {
532  $this->validatedNodes[] = $dataObject;
533  }
534  }
535 
536  return true;
537  }
538 
543  public function getRefNodeID($refNode)
544  {
545  if ($uri = $refNode->getAttribute("URI")) {
546  $arUrl = parse_url($uri);
547  if (empty($arUrl['path'])) {
548  if ($identifier = $arUrl['fragment']) {
549  return $identifier;
550  }
551  }
552  }
553  return null;
554  }
555 
560  public function getRefIDs()
561  {
562  $refids = array();
563 
564  $xpath = $this->getXPathObj();
565  $query = "./secdsig:SignedInfo[1]/secdsig:Reference";
566  $nodeset = $xpath->query($query, $this->sigNode);
567  if ($nodeset->length == 0) {
568  throw new Exception("Reference nodes not found");
569  }
570  foreach ($nodeset AS $refNode) {
571  $refids[] = $this->getRefNodeID($refNode);
572  }
573  return $refids;
574  }
575 
580  public function validateReference()
581  {
582  $docElem = $this->sigNode->ownerDocument->documentElement;
583  if (! $docElem->isSameNode($this->sigNode)) {
584  if ($this->sigNode->parentNode != null) {
585  $this->sigNode->parentNode->removeChild($this->sigNode);
586  }
587  }
588  $xpath = $this->getXPathObj();
589  $query = "./secdsig:SignedInfo[1]/secdsig:Reference";
590  $nodeset = $xpath->query($query, $this->sigNode);
591  if ($nodeset->length == 0) {
592  throw new Exception("Reference nodes not found");
593  }
594 
595  /* Initialize/reset the list of validated nodes. */
596  $this->validatedNodes = array();
597 
598  foreach ($nodeset AS $refNode) {
599  if (! $this->processRefNode($refNode)) {
600  /* Clear the list of validated nodes. */
601  $this->validatedNodes = null;
602  throw new Exception("Reference validation failed");
603  }
604  }
605  return true;
606  }
607 
615  private function addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=null, $options=null)
616  {
617  $prefix = null;
618  $prefix_ns = null;
619  $id_name = 'Id';
620  $overwrite_id = true;
621  $force_uri = false;
622 
623  if (is_array($options)) {
624  $prefix = empty($options['prefix']) ? null : $options['prefix'];
625  $prefix_ns = empty($options['prefix_ns']) ? null : $options['prefix_ns'];
626  $id_name = empty($options['id_name']) ? 'Id' : $options['id_name'];
627  $overwrite_id = !isset($options['overwrite']) ? true : (bool) $options['overwrite'];
628  $force_uri = !isset($options['force_uri']) ? false : (bool) $options['force_uri'];
629  }
630 
631  $attname = $id_name;
632  if (! empty($prefix)) {
633  $attname = $prefix.':'.$attname;
634  }
635 
636  $refNode = $this->createNewSignNode('Reference');
637  $sinfoNode->appendChild($refNode);
638 
639  if (! $node instanceof DOMDocument) {
640  $uri = null;
641  if (! $overwrite_id) {
642  $uri = $prefix_ns ? $node->getAttributeNS($prefix_ns, $id_name) : $node->getAttribute($id_name);
643  }
644  if (empty($uri)) {
645  $uri = self::generateGUID();
646  $node->setAttributeNS($prefix_ns, $attname, $uri);
647  }
648  $refNode->setAttribute("URI", '#'.$uri);
649  } elseif ($force_uri) {
650  $refNode->setAttribute("URI", '');
651  }
652 
653  $transNodes = $this->createNewSignNode('Transforms');
654  $refNode->appendChild($transNodes);
655 
656  if (is_array($arTransforms)) {
657  foreach ($arTransforms AS $transform) {
658  $transNode = $this->createNewSignNode('Transform');
659  $transNodes->appendChild($transNode);
660  if (is_array($transform) &&
661  (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116'])) &&
662  (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']))) {
663  $transNode->setAttribute('Algorithm', 'http://www.w3.org/TR/1999/REC-xpath-19991116');
664  $XPathNode = $this->createNewSignNode('XPath', $transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']);
665  $transNode->appendChild($XPathNode);
666  if (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'])) {
667  foreach ($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'] AS $prefix => $namespace) {
668  $XPathNode->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:$prefix", $namespace);
669  }
670  }
671  } else {
672  $transNode->setAttribute('Algorithm', $transform);
673  }
674  }
675  } elseif (! empty($this->canonicalMethod)) {
676  $transNode = $this->createNewSignNode('Transform');
677  $transNodes->appendChild($transNode);
678  $transNode->setAttribute('Algorithm', $this->canonicalMethod);
679  }
680 
681  $canonicalData = $this->processTransforms($refNode, $node);
682  $digValue = $this->calculateDigest($algorithm, $canonicalData);
683 
684  $digestMethod = $this->createNewSignNode('DigestMethod');
685  $refNode->appendChild($digestMethod);
686  $digestMethod->setAttribute('Algorithm', $algorithm);
687 
688  $digestValue = $this->createNewSignNode('DigestValue', $digValue);
689  $refNode->appendChild($digestValue);
690  }
691 
698  public function addReference($node, $algorithm, $arTransforms=null, $options=null)
699  {
700  if ($xpath = $this->getXPathObj()) {
701  $query = "./secdsig:SignedInfo";
702  $nodeset = $xpath->query($query, $this->sigNode);
703  if ($sInfo = $nodeset->item(0)) {
704  $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options);
705  }
706  }
707  }
708 
715  public function addReferenceList($arNodes, $algorithm, $arTransforms=null, $options=null)
716  {
717  if ($xpath = $this->getXPathObj()) {
718  $query = "./secdsig:SignedInfo";
719  $nodeset = $xpath->query($query, $this->sigNode);
720  if ($sInfo = $nodeset->item(0)) {
721  foreach ($arNodes AS $node) {
722  $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options);
723  }
724  }
725  }
726  }
727 
734  public function addObject($data, $mimetype=null, $encoding=null)
735  {
736  $objNode = $this->createNewSignNode('Object');
737  $this->sigNode->appendChild($objNode);
738  if (! empty($mimetype)) {
739  $objNode->setAttribute('MimeType', $mimetype);
740  }
741  if (! empty($encoding)) {
742  $objNode->setAttribute('Encoding', $encoding);
743  }
744 
745  if ($data instanceof DOMElement) {
746  $newData = $this->sigNode->ownerDocument->importNode($data, true);
747  } else {
748  $newData = $this->sigNode->ownerDocument->createTextNode($data);
749  }
750  $objNode->appendChild($newData);
751 
752  return $objNode;
753  }
754 
759  public function locateKey($node=null)
760  {
761  if (empty($node)) {
762  $node = $this->sigNode;
763  }
764  if (! $node instanceof DOMNode) {
765  return null;
766  }
767  if ($doc = $node->ownerDocument) {
768  $xpath = new DOMXPath($doc);
769  $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
770  $query = "string(./secdsig:SignedInfo/secdsig:SignatureMethod/@Algorithm)";
771  $algorithm = $xpath->evaluate($query, $node);
772  if ($algorithm) {
773  try {
774  $objKey = new XMLSecurityKey($algorithm, array('type' => 'public'));
775  } catch (Exception $e) {
776  return null;
777  }
778  return $objKey;
779  }
780  }
781  return null;
782  }
783 
800  public function verify($objKey)
801  {
802  $doc = $this->sigNode->ownerDocument;
803  $xpath = new DOMXPath($doc);
804  $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
805  $query = "string(./secdsig:SignatureValue)";
806  $sigValue = $xpath->evaluate($query, $this->sigNode);
807  if (empty($sigValue)) {
808  throw new Exception("Unable to locate SignatureValue");
809  }
810  return $objKey->verifySignature($this->signedInfo, base64_decode($sigValue));
811  }
812 
818  public function signData($objKey, $data)
819  {
820  return $objKey->signData($data);
821  }
822 
827  public function sign($objKey, $appendToNode = null)
828  {
829  // If we have a parent node append it now so C14N properly works
830  if ($appendToNode != null) {
831  $this->resetXPathObj();
832  $this->appendSignature($appendToNode);
833  $this->sigNode = $appendToNode->lastChild;
834  }
835  if ($xpath = $this->getXPathObj()) {
836  $query = "./secdsig:SignedInfo";
837  $nodeset = $xpath->query($query, $this->sigNode);
838  if ($sInfo = $nodeset->item(0)) {
839  $query = "./secdsig:SignatureMethod";
840  $nodeset = $xpath->query($query, $sInfo);
841  $sMethod = $nodeset->item(0);
842  $sMethod->setAttribute('Algorithm', $objKey->type);
843  $data = $this->canonicalizeData($sInfo, $this->canonicalMethod);
844  $sigValue = base64_encode($this->signData($objKey, $data));
845  $sigValueNode = $this->createNewSignNode('SignatureValue', $sigValue);
846  if ($infoSibling = $sInfo->nextSibling) {
847  $infoSibling->parentNode->insertBefore($sigValueNode, $infoSibling);
848  } else {
849  $this->sigNode->appendChild($sigValueNode);
850  }
851  }
852  }
853  }
854 
855  public function appendCert()
856  {
857 
858  }
859 
864  public function appendKey($objKey, $parent=null)
865  {
866  $objKey->serializeKey($parent);
867  }
868 
869 
881  public function insertSignature($node, $beforeNode = null)
882  {
883 
884  $document = $node->ownerDocument;
885  $signatureElement = $document->importNode($this->sigNode, true);
886 
887  if ($beforeNode == null) {
888  return $node->insertBefore($signatureElement);
889  } else {
890  return $node->insertBefore($signatureElement, $beforeNode);
891  }
892  }
893 
899  public function appendSignature($parentNode, $insertBefore = false)
900  {
901  $beforeNode = $insertBefore ? $parentNode->firstChild : null;
902  return $this->insertSignature($parentNode, $beforeNode);
903  }
904 
910  public static function get509XCert($cert, $isPEMFormat=true)
911  {
912  $certs = self::staticGet509XCerts($cert, $isPEMFormat);
913  if (! empty($certs)) {
914  return $certs[0];
915  }
916  return '';
917  }
918 
924  public static function staticGet509XCerts($certs, $isPEMFormat=true)
925  {
926  if ($isPEMFormat) {
927  $data = '';
928  $certlist = array();
929  $arCert = explode("\n", $certs);
930  $inData = false;
931  foreach ($arCert AS $curData) {
932  if (! $inData) {
933  if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
934  $inData = true;
935  }
936  } else {
937  if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
938  $inData = false;
939  $certlist[] = $data;
940  $data = '';
941  continue;
942  }
943  $data .= trim($curData);
944  }
945  }
946  return $certlist;
947  } else {
948  return array($certs);
949  }
950  }
951 
961  public static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=true, $isURL=false, $xpath=null, $options=null)
962  {
963  if ($isURL) {
964  $cert = file_get_contents($cert);
965  }
966  if (! $parentRef instanceof DOMElement) {
967  throw new Exception('Invalid parent Node parameter');
968  }
969  $baseDoc = $parentRef->ownerDocument;
970 
971  if (empty($xpath)) {
972  $xpath = new DOMXPath($parentRef->ownerDocument);
973  $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
974  }
975 
976  $query = "./secdsig:KeyInfo";
977  $nodeset = $xpath->query($query, $parentRef);
978  $keyInfo = $nodeset->item(0);
979  $dsig_pfx = '';
980  if (! $keyInfo) {
981  $pfx = $parentRef->lookupPrefix(self::XMLDSIGNS);
982  if (! empty($pfx)) {
983  $dsig_pfx = $pfx.":";
984  }
985  $inserted = false;
986  $keyInfo = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'KeyInfo');
987 
988  $query = "./secdsig:Object";
989  $nodeset = $xpath->query($query, $parentRef);
990  if ($sObject = $nodeset->item(0)) {
991  $sObject->parentNode->insertBefore($keyInfo, $sObject);
992  $inserted = true;
993  }
994 
995  if (! $inserted) {
996  $parentRef->appendChild($keyInfo);
997  }
998  } else {
999  $pfx = $keyInfo->lookupPrefix(self::XMLDSIGNS);
1000  if (! empty($pfx)) {
1001  $dsig_pfx = $pfx.":";
1002  }
1003  }
1004 
1005  // Add all certs if there are more than one
1006  $certs = self::staticGet509XCerts($cert, $isPEMFormat);
1007 
1008  // Attach X509 data node
1009  $x509DataNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509Data');
1010  $keyInfo->appendChild($x509DataNode);
1011 
1012  $issuerSerial = false;
1013  $subjectName = false;
1014  if (is_array($options)) {
1015  if (! empty($options['issuerSerial'])) {
1016  $issuerSerial = true;
1017  }
1018  if (! empty($options['subjectName'])) {
1019  $subjectName = true;
1020  }
1021  }
1022 
1023  // Attach all certificate nodes and any additional data
1024  foreach ($certs as $X509Cert) {
1025  if ($issuerSerial || $subjectName) {
1026  if ($certData = openssl_x509_parse("-----BEGIN CERTIFICATE-----\n".chunk_split($X509Cert, 64, "\n")."-----END CERTIFICATE-----\n")) {
1027  if ($subjectName && ! empty($certData['subject'])) {
1028  if (is_array($certData['subject'])) {
1029  $parts = array();
1030  foreach ($certData['subject'] AS $key => $value) {
1031  if (is_array($value)) {
1032  foreach ($value as $valueElement) {
1033  array_unshift($parts, "$key=$valueElement");
1034  }
1035  } else {
1036  array_unshift($parts, "$key=$value");
1037  }
1038  }
1039  $subjectNameValue = implode(',', $parts);
1040  } else {
1041  $subjectNameValue = $certData['issuer'];
1042  }
1043  $x509SubjectNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509SubjectName', $subjectNameValue);
1044  $x509DataNode->appendChild($x509SubjectNode);
1045  }
1046  if ($issuerSerial && ! empty($certData['issuer']) && ! empty($certData['serialNumber'])) {
1047  if (is_array($certData['issuer'])) {
1048  $parts = array();
1049  foreach ($certData['issuer'] AS $key => $value) {
1050  array_unshift($parts, "$key=$value");
1051  }
1052  $issuerName = implode(',', $parts);
1053  } else {
1054  $issuerName = $certData['issuer'];
1055  }
1056 
1057  $x509IssuerNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509IssuerSerial');
1058  $x509DataNode->appendChild($x509IssuerNode);
1059 
1060  $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509IssuerName', $issuerName);
1061  $x509IssuerNode->appendChild($x509Node);
1062  $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509SerialNumber', $certData['serialNumber']);
1063  $x509IssuerNode->appendChild($x509Node);
1064  }
1065  }
1066 
1067  }
1068  $x509CertNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509Certificate', $X509Cert);
1069  $x509DataNode->appendChild($x509CertNode);
1070  }
1071  }
1072 
1079  public function add509Cert($cert, $isPEMFormat=true, $isURL=false, $options=null)
1080  {
1081  if ($xpath = $this->getXPathObj()) {
1082  self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath, $options);
1083  }
1084  }
1085 
1095  public function appendToKeyInfo($node)
1096  {
1097  $parentRef = $this->sigNode;
1098  $baseDoc = $parentRef->ownerDocument;
1099 
1100  $xpath = $this->getXPathObj();
1101  if (empty($xpath)) {
1102  $xpath = new DOMXPath($parentRef->ownerDocument);
1103  $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
1104  }
1105 
1106  $query = "./secdsig:KeyInfo";
1107  $nodeset = $xpath->query($query, $parentRef);
1108  $keyInfo = $nodeset->item(0);
1109  if (! $keyInfo) {
1110  $dsig_pfx = '';
1111  $pfx = $parentRef->lookupPrefix(self::XMLDSIGNS);
1112  if (! empty($pfx)) {
1113  $dsig_pfx = $pfx.":";
1114  }
1115  $inserted = false;
1116  $keyInfo = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'KeyInfo');
1117 
1118  $query = "./secdsig:Object";
1119  $nodeset = $xpath->query($query, $parentRef);
1120  if ($sObject = $nodeset->item(0)) {
1121  $sObject->parentNode->insertBefore($keyInfo, $sObject);
1122  $inserted = true;
1123  }
1124 
1125  if (! $inserted) {
1126  $parentRef->appendChild($keyInfo);
1127  }
1128  }
1129 
1130  $keyInfo->appendChild($node);
1131 
1132  return $keyInfo;
1133  }
1134 
1146  public function getValidatedNodes()
1147  {
1148  return $this->validatedNodes;
1149  }
1150 }
calculateDigest($digestAlgorithm, $data, $encode=true)
if($err=$client->getError()) $namespace
addReferenceList($arNodes, $algorithm, $arTransforms=null, $options=null)
canonicalizeData($node, $canonicalmethod, $arXPath=null, $prefixList=null)
addReference($node, $algorithm, $arTransforms=null, $options=null)
$template
static staticAdd509Cert($parentRef, $cert, $isPEMFormat=true, $isURL=false, $xpath=null, $options=null)
add509Cert($cert, $isPEMFormat=true, $isURL=false, $options=null)
static generate_GUID($prefix='pfx')
Generate guid.
appendSignature($parentNode, $insertBefore=false)
insertSignature($node, $beforeNode=null)
This function inserts the signature element.
static get509XCert($cert, $isPEMFormat=true)
verify($objKey)
Returns: Bool when verifying HMAC_SHA1; Int otherwise, with following meanings: 1 on succesful signat...
appendToKeyInfo($node)
This function appends a node to the KeyInfo.
getXPathObj()
Returns the XPathObj or null if xPathCtx is set and sigNode is empty.
static staticGet509XCerts($certs, $isPEMFormat=true)
$query
sign($objKey, $appendToNode=null)
processTransforms($refNode, $objData, $includeCommentNodes=true)
addObject($data, $mimetype=null, $encoding=null)
static generateGUID($prefix='pfx')
Generate guid.
getValidatedNodes()
This function retrieves an associative array of the validated nodes.
resetXPathObj()
Reset the XPathObj to null.
addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=null, $options=null)
hash(StreamInterface $stream, $algo, $rawOutput=false)
Calculate a hash of a Stream.
Definition: functions.php:406
$key
Definition: croninfo.php:18
$data
Definition: bench.php:6