Rev 868 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
868 | daniel-mar | 1 | <?php |
2 | namespace RobRichards\XMLSecLibs; |
||
3 | |||
4 | use DOMDocument; |
||
5 | use DOMElement; |
||
6 | use DOMNode; |
||
7 | use DOMXPath; |
||
8 | use Exception; |
||
9 | use RobRichards\XMLSecLibs\Utils\XPath as XPath; |
||
10 | |||
11 | /** |
||
12 | * xmlseclibs.php |
||
13 | * |
||
14 | * Copyright (c) 2007-2020, Robert Richards <rrichards@cdatazone.org>. |
||
15 | * All rights reserved. |
||
16 | * |
||
17 | * Redistribution and use in source and binary forms, with or without |
||
18 | * modification, are permitted provided that the following conditions |
||
19 | * are met: |
||
20 | * |
||
21 | * * Redistributions of source code must retain the above copyright |
||
22 | * notice, this list of conditions and the following disclaimer. |
||
23 | * |
||
24 | * * Redistributions in binary form must reproduce the above copyright |
||
25 | * notice, this list of conditions and the following disclaimer in |
||
26 | * the documentation and/or other materials provided with the |
||
27 | * distribution. |
||
28 | * |
||
29 | * * Neither the name of Robert Richards nor the names of his |
||
30 | * contributors may be used to endorse or promote products derived |
||
31 | * from this software without specific prior written permission. |
||
32 | * |
||
33 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||
34 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||
35 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
||
36 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
||
37 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
||
38 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
||
39 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||
40 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||
41 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||
42 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
||
43 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||
44 | * POSSIBILITY OF SUCH DAMAGE. |
||
45 | * |
||
46 | * @author Robert Richards <rrichards@cdatazone.org> |
||
47 | * @copyright 2007-2020 Robert Richards <rrichards@cdatazone.org> |
||
48 | * @license http://www.opensource.org/licenses/bsd-license.php BSD License |
||
49 | */ |
||
50 | |||
51 | class XMLSecurityDSig |
||
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 | |||
77 | /** @var DOMElement|null */ |
||
78 | public $sigNode = null; |
||
79 | |||
80 | /** @var array */ |
||
81 | public $idKeys = array(); |
||
82 | |||
83 | /** @var array */ |
||
84 | public $idNS = array(); |
||
85 | |||
86 | /** @var string|null */ |
||
87 | private $signedInfo = null; |
||
88 | |||
89 | /** @var DomXPath|null */ |
||
90 | private $xPathCtx = null; |
||
91 | |||
92 | /** @var string|null */ |
||
93 | private $canonicalMethod = null; |
||
94 | |||
95 | /** @var string */ |
||
96 | private $prefix = ''; |
||
97 | |||
98 | /** @var string */ |
||
99 | private $searchpfx = 'secdsig'; |
||
100 | |||
101 | /** |
||
102 | * This variable contains an associative array of validated nodes. |
||
103 | * @var array|null |
||
104 | */ |
||
105 | private $validatedNodes = null; |
||
106 | |||
107 | /** |
||
108 | * @param string $prefix |
||
109 | */ |
||
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 | |||
124 | /** |
||
125 | * Reset the XPathObj to null |
||
126 | */ |
||
127 | private function resetXPathObj() |
||
128 | { |
||
129 | $this->xPathCtx = null; |
||
130 | } |
||
131 | |||
132 | /** |
||
133 | * Returns the XPathObj or null if xPathCtx is set and sigNode is empty. |
||
134 | * |
||
135 | * @return DOMXPath|null |
||
136 | */ |
||
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 | |||
147 | /** |
||
148 | * Generate guid |
||
149 | * |
||
150 | * @param string $prefix Prefix to use for guid. defaults to pfx |
||
151 | * |
||
152 | * @return string The generated guid |
||
153 | */ |
||
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 | |||
165 | /** |
||
166 | * Generate guid |
||
167 | * |
||
168 | * @param string $prefix Prefix to use for guid. defaults to pfx |
||
169 | * |
||
170 | * @return string The generated guid |
||
171 | * |
||
172 | * @deprecated Method deprecated in Release 1.4.1 |
||
173 | */ |
||
174 | public static function generate_GUID($prefix='pfx') |
||
175 | { |
||
176 | return self::generateGUID($prefix); |
||
177 | } |
||
178 | |||
179 | /** |
||
180 | * @param DOMDocument $objDoc |
||
181 | * @param int $pos |
||
182 | * @return DOMNode|null |
||
183 | */ |
||
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 | |||
207 | /** |
||
208 | * @param string $name |
||
209 | * @param null|string $value |
||
210 | * @return DOMElement |
||
211 | */ |
||
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 | |||
223 | /** |
||
224 | * @param string $method |
||
225 | * @throws Exception |
||
226 | */ |
||
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 | |||
254 | /** |
||
255 | * @param DOMNode $node |
||
256 | * @param string $canonicalmethod |
||
257 | * @param null|array $arXPath |
||
258 | * @param null|array $prefixList |
||
259 | * @return string |
||
260 | */ |
||
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 | |||
299 | /** |
||
300 | * @return null|string |
||
301 | */ |
||
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 | $prefixList = null; |
||
318 | if ($canonNode = $nodeset->item(0)) { |
||
319 | $canonicalmethod = $canonNode->getAttribute('Algorithm'); |
||
320 | foreach ($canonNode->childNodes as $node) |
||
321 | { |
||
322 | if ($node->localName == 'InclusiveNamespaces') { |
||
323 | if ($pfx = $node->getAttribute('PrefixList')) { |
||
324 | $arpfx = array_filter(explode(' ', $pfx)); |
||
325 | if (count($arpfx) > 0) { |
||
326 | $prefixList = array_merge($prefixList ? $prefixList : array(), $arpfx); |
||
327 | } |
||
328 | } |
||
329 | } |
||
330 | } |
||
331 | } |
||
332 | $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod, null, $prefixList); |
||
333 | return $this->signedInfo; |
||
334 | } |
||
335 | } |
||
336 | return null; |
||
337 | } |
||
338 | |||
339 | /** |
||
340 | * @param string $digestAlgorithm |
||
341 | * @param string $data |
||
342 | * @param bool $encode |
||
343 | * @return string |
||
344 | * @throws Exception |
||
345 | */ |
||
346 | public function calculateDigest($digestAlgorithm, $data, $encode = true) |
||
347 | { |
||
348 | switch ($digestAlgorithm) { |
||
349 | case self::SHA1: |
||
350 | $alg = 'sha1'; |
||
351 | break; |
||
352 | case self::SHA256: |
||
353 | $alg = 'sha256'; |
||
354 | break; |
||
355 | case self::SHA384: |
||
356 | $alg = 'sha384'; |
||
357 | break; |
||
358 | case self::SHA512: |
||
359 | $alg = 'sha512'; |
||
360 | break; |
||
361 | case self::RIPEMD160: |
||
362 | $alg = 'ripemd160'; |
||
363 | break; |
||
364 | default: |
||
365 | throw new Exception("Cannot validate digest: Unsupported Algorithm <$digestAlgorithm>"); |
||
366 | } |
||
367 | |||
368 | $digest = hash($alg, $data, true); |
||
369 | if ($encode) { |
||
370 | $digest = base64_encode($digest); |
||
371 | } |
||
372 | return $digest; |
||
373 | |||
374 | } |
||
375 | |||
376 | /** |
||
377 | * @param $refNode |
||
378 | * @param string $data |
||
379 | * @return bool |
||
380 | */ |
||
381 | public function validateDigest($refNode, $data) |
||
382 | { |
||
383 | $xpath = new DOMXPath($refNode->ownerDocument); |
||
384 | $xpath->registerNamespace('secdsig', self::XMLDSIGNS); |
||
385 | $query = 'string(./secdsig:DigestMethod/@Algorithm)'; |
||
386 | $digestAlgorithm = $xpath->evaluate($query, $refNode); |
||
387 | $digValue = $this->calculateDigest($digestAlgorithm, $data, false); |
||
388 | $query = 'string(./secdsig:DigestValue)'; |
||
389 | $digestValue = $xpath->evaluate($query, $refNode); |
||
390 | return ($digValue === base64_decode($digestValue)); |
||
391 | } |
||
392 | |||
393 | /** |
||
394 | * @param $refNode |
||
395 | * @param DOMNode $objData |
||
396 | * @param bool $includeCommentNodes |
||
397 | * @return string |
||
398 | */ |
||
399 | public function processTransforms($refNode, $objData, $includeCommentNodes = true) |
||
400 | { |
||
401 | $data = $objData; |
||
402 | $xpath = new DOMXPath($refNode->ownerDocument); |
||
403 | $xpath->registerNamespace('secdsig', self::XMLDSIGNS); |
||
404 | $query = './secdsig:Transforms/secdsig:Transform'; |
||
405 | $nodelist = $xpath->query($query, $refNode); |
||
406 | $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; |
||
407 | $arXPath = null; |
||
408 | $prefixList = null; |
||
409 | foreach ($nodelist AS $transform) { |
||
410 | $algorithm = $transform->getAttribute("Algorithm"); |
||
411 | switch ($algorithm) { |
||
412 | case 'http://www.w3.org/2001/10/xml-exc-c14n#': |
||
413 | case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': |
||
414 | |||
415 | if (!$includeCommentNodes) { |
||
416 | /* We remove comment nodes by forcing it to use a canonicalization |
||
417 | * without comments. |
||
418 | */ |
||
419 | $canonicalMethod = 'http://www.w3.org/2001/10/xml-exc-c14n#'; |
||
420 | } else { |
||
421 | $canonicalMethod = $algorithm; |
||
422 | } |
||
423 | |||
424 | $node = $transform->firstChild; |
||
425 | while ($node) { |
||
426 | if ($node->localName == 'InclusiveNamespaces') { |
||
427 | if ($pfx = $node->getAttribute('PrefixList')) { |
||
428 | $arpfx = array(); |
||
429 | $pfxlist = explode(" ", $pfx); |
||
430 | foreach ($pfxlist AS $pfx) { |
||
431 | $val = trim($pfx); |
||
432 | if (! empty($val)) { |
||
433 | $arpfx[] = $val; |
||
434 | } |
||
435 | } |
||
436 | if (count($arpfx) > 0) { |
||
437 | $prefixList = $arpfx; |
||
438 | } |
||
439 | } |
||
440 | break; |
||
441 | } |
||
442 | $node = $node->nextSibling; |
||
443 | } |
||
444 | break; |
||
445 | case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': |
||
446 | case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': |
||
447 | if (!$includeCommentNodes) { |
||
448 | /* We remove comment nodes by forcing it to use a canonicalization |
||
449 | * without comments. |
||
450 | */ |
||
451 | $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; |
||
452 | } else { |
||
453 | $canonicalMethod = $algorithm; |
||
454 | } |
||
455 | |||
456 | break; |
||
457 | case 'http://www.w3.org/TR/1999/REC-xpath-19991116': |
||
458 | $node = $transform->firstChild; |
||
459 | while ($node) { |
||
460 | if ($node->localName == 'XPath') { |
||
461 | $arXPath = array(); |
||
462 | $arXPath['query'] = '(.//. | .//@* | .//namespace::*)['.$node->nodeValue.']'; |
||
463 | $arXPath['namespaces'] = array(); |
||
464 | $nslist = $xpath->query('./namespace::*', $node); |
||
465 | foreach ($nslist AS $nsnode) { |
||
466 | if ($nsnode->localName != "xml") { |
||
467 | $arXPath['namespaces'][$nsnode->localName] = $nsnode->nodeValue; |
||
468 | } |
||
469 | } |
||
470 | break; |
||
471 | } |
||
472 | $node = $node->nextSibling; |
||
473 | } |
||
474 | break; |
||
475 | } |
||
476 | } |
||
477 | if ($data instanceof DOMNode) { |
||
478 | $data = $this->canonicalizeData($objData, $canonicalMethod, $arXPath, $prefixList); |
||
479 | } |
||
480 | return $data; |
||
481 | } |
||
482 | |||
483 | /** |
||
484 | * @param DOMNode $refNode |
||
485 | * @return bool |
||
486 | */ |
||
487 | public function processRefNode($refNode) |
||
488 | { |
||
489 | $dataObject = null; |
||
490 | |||
491 | /* |
||
492 | * Depending on the URI, we may not want to include comments in the result |
||
493 | * See: http://www.w3.org/TR/xmldsig-core/#sec-ReferenceProcessingModel |
||
494 | */ |
||
495 | $includeCommentNodes = true; |
||
496 | |||
497 | if ($uri = $refNode->getAttribute("URI")) { |
||
498 | $arUrl = parse_url($uri); |
||
499 | if (empty($arUrl['path'])) { |
||
500 | if ($identifier = $arUrl['fragment']) { |
||
501 | |||
502 | /* This reference identifies a node with the given id by using |
||
503 | * a URI on the form "#identifier". This should not include comments. |
||
504 | */ |
||
505 | $includeCommentNodes = false; |
||
506 | |||
507 | $xPath = new DOMXPath($refNode->ownerDocument); |
||
508 | if ($this->idNS && is_array($this->idNS)) { |
||
509 | foreach ($this->idNS as $nspf => $ns) { |
||
510 | $xPath->registerNamespace($nspf, $ns); |
||
511 | } |
||
512 | } |
||
513 | $iDlist = '@Id="'.XPath::filterAttrValue($identifier, XPath::DOUBLE_QUOTE).'"'; |
||
514 | if (is_array($this->idKeys)) { |
||
515 | foreach ($this->idKeys as $idKey) { |
||
516 | $iDlist .= " or @".XPath::filterAttrName($idKey).'="'. |
||
517 | XPath::filterAttrValue($identifier, XPath::DOUBLE_QUOTE).'"'; |
||
518 | } |
||
519 | } |
||
520 | $query = '//*['.$iDlist.']'; |
||
521 | $dataObject = $xPath->query($query)->item(0); |
||
522 | } else { |
||
523 | $dataObject = $refNode->ownerDocument; |
||
524 | } |
||
525 | } |
||
526 | } else { |
||
527 | /* This reference identifies the root node with an empty URI. This should |
||
528 | * not include comments. |
||
529 | */ |
||
530 | $includeCommentNodes = false; |
||
531 | |||
532 | $dataObject = $refNode->ownerDocument; |
||
533 | } |
||
534 | $data = $this->processTransforms($refNode, $dataObject, $includeCommentNodes); |
||
535 | if (!$this->validateDigest($refNode, $data)) { |
||
536 | return false; |
||
537 | } |
||
538 | |||
539 | if ($dataObject instanceof DOMNode) { |
||
540 | /* Add this node to the list of validated nodes. */ |
||
541 | if (! empty($identifier)) { |
||
542 | $this->validatedNodes[$identifier] = $dataObject; |
||
543 | } else { |
||
544 | $this->validatedNodes[] = $dataObject; |
||
545 | } |
||
546 | } |
||
547 | |||
548 | return true; |
||
549 | } |
||
550 | |||
551 | /** |
||
552 | * @param DOMNode $refNode |
||
553 | * @return null |
||
554 | */ |
||
555 | public function getRefNodeID($refNode) |
||
556 | { |
||
557 | if ($uri = $refNode->getAttribute("URI")) { |
||
558 | $arUrl = parse_url($uri); |
||
559 | if (empty($arUrl['path'])) { |
||
560 | if ($identifier = $arUrl['fragment']) { |
||
561 | return $identifier; |
||
562 | } |
||
563 | } |
||
564 | } |
||
565 | return null; |
||
566 | } |
||
567 | |||
568 | /** |
||
569 | * @return array |
||
570 | * @throws Exception |
||
571 | */ |
||
572 | public function getRefIDs() |
||
573 | { |
||
574 | $refids = array(); |
||
575 | |||
576 | $xpath = $this->getXPathObj(); |
||
577 | $query = "./secdsig:SignedInfo[1]/secdsig:Reference"; |
||
578 | $nodeset = $xpath->query($query, $this->sigNode); |
||
579 | if ($nodeset->length == 0) { |
||
580 | throw new Exception("Reference nodes not found"); |
||
581 | } |
||
582 | foreach ($nodeset AS $refNode) { |
||
583 | $refids[] = $this->getRefNodeID($refNode); |
||
584 | } |
||
585 | return $refids; |
||
586 | } |
||
587 | |||
588 | /** |
||
589 | * @return bool |
||
590 | * @throws Exception |
||
591 | */ |
||
592 | public function validateReference() |
||
593 | { |
||
594 | $docElem = $this->sigNode->ownerDocument->documentElement; |
||
595 | if (! $docElem->isSameNode($this->sigNode)) { |
||
596 | if ($this->sigNode->parentNode != null) { |
||
597 | $this->sigNode->parentNode->removeChild($this->sigNode); |
||
598 | } |
||
599 | } |
||
600 | $xpath = $this->getXPathObj(); |
||
601 | $query = "./secdsig:SignedInfo[1]/secdsig:Reference"; |
||
602 | $nodeset = $xpath->query($query, $this->sigNode); |
||
603 | if ($nodeset->length == 0) { |
||
604 | throw new Exception("Reference nodes not found"); |
||
605 | } |
||
606 | |||
607 | /* Initialize/reset the list of validated nodes. */ |
||
608 | $this->validatedNodes = array(); |
||
609 | |||
610 | foreach ($nodeset AS $refNode) { |
||
611 | if (! $this->processRefNode($refNode)) { |
||
612 | /* Clear the list of validated nodes. */ |
||
613 | $this->validatedNodes = null; |
||
614 | throw new Exception("Reference validation failed"); |
||
615 | } |
||
616 | } |
||
617 | return true; |
||
618 | } |
||
619 | |||
620 | /** |
||
621 | * @param DOMNode $sinfoNode |
||
622 | * @param DOMDocument $node |
||
623 | * @param string $algorithm |
||
624 | * @param null|array $arTransforms |
||
625 | * @param null|array $options |
||
626 | */ |
||
627 | private function addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=null, $options=null) |
||
628 | { |
||
629 | $prefix = null; |
||
630 | $prefix_ns = null; |
||
631 | $id_name = 'Id'; |
||
632 | $overwrite_id = true; |
||
633 | $force_uri = false; |
||
634 | |||
635 | if (is_array($options)) { |
||
636 | $prefix = empty($options['prefix']) ? null : $options['prefix']; |
||
637 | $prefix_ns = empty($options['prefix_ns']) ? null : $options['prefix_ns']; |
||
638 | $id_name = empty($options['id_name']) ? 'Id' : $options['id_name']; |
||
639 | $overwrite_id = !isset($options['overwrite']) ? true : (bool) $options['overwrite']; |
||
640 | $force_uri = !isset($options['force_uri']) ? false : (bool) $options['force_uri']; |
||
641 | } |
||
642 | |||
643 | $attname = $id_name; |
||
644 | if (! empty($prefix)) { |
||
645 | $attname = $prefix.':'.$attname; |
||
646 | } |
||
647 | |||
648 | $refNode = $this->createNewSignNode('Reference'); |
||
649 | $sinfoNode->appendChild($refNode); |
||
650 | |||
651 | if (! $node instanceof DOMDocument) { |
||
652 | $uri = null; |
||
653 | if (! $overwrite_id) { |
||
654 | $uri = $prefix_ns ? $node->getAttributeNS($prefix_ns, $id_name) : $node->getAttribute($id_name); |
||
655 | } |
||
656 | if (empty($uri)) { |
||
657 | $uri = self::generateGUID(); |
||
658 | $node->setAttributeNS($prefix_ns, $attname, $uri); |
||
659 | } |
||
660 | $refNode->setAttribute("URI", '#'.$uri); |
||
661 | } elseif ($force_uri) { |
||
662 | $refNode->setAttribute("URI", ''); |
||
663 | } |
||
664 | |||
665 | $transNodes = $this->createNewSignNode('Transforms'); |
||
666 | $refNode->appendChild($transNodes); |
||
667 | |||
668 | if (is_array($arTransforms)) { |
||
669 | foreach ($arTransforms AS $transform) { |
||
670 | $transNode = $this->createNewSignNode('Transform'); |
||
671 | $transNodes->appendChild($transNode); |
||
672 | if (is_array($transform) && |
||
673 | (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116'])) && |
||
674 | (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']))) { |
||
675 | $transNode->setAttribute('Algorithm', 'http://www.w3.org/TR/1999/REC-xpath-19991116'); |
||
676 | $XPathNode = $this->createNewSignNode('XPath', $transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']); |
||
677 | $transNode->appendChild($XPathNode); |
||
678 | if (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'])) { |
||
679 | foreach ($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'] AS $prefix => $namespace) { |
||
680 | $XPathNode->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:$prefix", $namespace); |
||
681 | } |
||
682 | } |
||
683 | } else { |
||
684 | $transNode->setAttribute('Algorithm', $transform); |
||
685 | } |
||
686 | } |
||
687 | } elseif (! empty($this->canonicalMethod)) { |
||
688 | $transNode = $this->createNewSignNode('Transform'); |
||
689 | $transNodes->appendChild($transNode); |
||
690 | $transNode->setAttribute('Algorithm', $this->canonicalMethod); |
||
691 | } |
||
692 | |||
693 | $canonicalData = $this->processTransforms($refNode, $node); |
||
694 | $digValue = $this->calculateDigest($algorithm, $canonicalData); |
||
695 | |||
696 | $digestMethod = $this->createNewSignNode('DigestMethod'); |
||
697 | $refNode->appendChild($digestMethod); |
||
698 | $digestMethod->setAttribute('Algorithm', $algorithm); |
||
699 | |||
700 | $digestValue = $this->createNewSignNode('DigestValue', $digValue); |
||
701 | $refNode->appendChild($digestValue); |
||
702 | } |
||
703 | |||
704 | /** |
||
705 | * @param DOMDocument $node |
||
706 | * @param string $algorithm |
||
707 | * @param null|array $arTransforms |
||
708 | * @param null|array $options |
||
709 | */ |
||
710 | public function addReference($node, $algorithm, $arTransforms=null, $options=null) |
||
711 | { |
||
712 | if ($xpath = $this->getXPathObj()) { |
||
713 | $query = "./secdsig:SignedInfo"; |
||
714 | $nodeset = $xpath->query($query, $this->sigNode); |
||
715 | if ($sInfo = $nodeset->item(0)) { |
||
716 | $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options); |
||
717 | } |
||
718 | } |
||
719 | } |
||
720 | |||
721 | /** |
||
722 | * @param array $arNodes |
||
723 | * @param string $algorithm |
||
724 | * @param null|array $arTransforms |
||
725 | * @param null|array $options |
||
726 | */ |
||
727 | public function addReferenceList($arNodes, $algorithm, $arTransforms=null, $options=null) |
||
728 | { |
||
729 | if ($xpath = $this->getXPathObj()) { |
||
730 | $query = "./secdsig:SignedInfo"; |
||
731 | $nodeset = $xpath->query($query, $this->sigNode); |
||
732 | if ($sInfo = $nodeset->item(0)) { |
||
733 | foreach ($arNodes AS $node) { |
||
734 | $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options); |
||
735 | } |
||
736 | } |
||
737 | } |
||
738 | } |
||
739 | |||
740 | /** |
||
741 | * @param DOMElement|string $data |
||
742 | * @param null|string $mimetype |
||
743 | * @param null|string $encoding |
||
744 | * @return DOMElement |
||
745 | */ |
||
746 | public function addObject($data, $mimetype=null, $encoding=null) |
||
747 | { |
||
748 | $objNode = $this->createNewSignNode('Object'); |
||
749 | $this->sigNode->appendChild($objNode); |
||
750 | if (! empty($mimetype)) { |
||
751 | $objNode->setAttribute('MimeType', $mimetype); |
||
752 | } |
||
753 | if (! empty($encoding)) { |
||
754 | $objNode->setAttribute('Encoding', $encoding); |
||
755 | } |
||
756 | |||
757 | if ($data instanceof DOMElement) { |
||
758 | $newData = $this->sigNode->ownerDocument->importNode($data, true); |
||
759 | } else { |
||
760 | $newData = $this->sigNode->ownerDocument->createTextNode($data); |
||
761 | } |
||
762 | $objNode->appendChild($newData); |
||
763 | |||
764 | return $objNode; |
||
765 | } |
||
766 | |||
767 | /** |
||
768 | * @param null|DOMNode $node |
||
769 | * @return null|XMLSecurityKey |
||
770 | */ |
||
771 | public function locateKey($node=null) |
||
772 | { |
||
773 | if (empty($node)) { |
||
774 | $node = $this->sigNode; |
||
775 | } |
||
776 | if (! $node instanceof DOMNode) { |
||
777 | return null; |
||
778 | } |
||
779 | if ($doc = $node->ownerDocument) { |
||
780 | $xpath = new DOMXPath($doc); |
||
781 | $xpath->registerNamespace('secdsig', self::XMLDSIGNS); |
||
782 | $query = "string(./secdsig:SignedInfo/secdsig:SignatureMethod/@Algorithm)"; |
||
783 | $algorithm = $xpath->evaluate($query, $node); |
||
784 | if ($algorithm) { |
||
785 | try { |
||
786 | $objKey = new XMLSecurityKey($algorithm, array('type' => 'public')); |
||
1050 | daniel-mar | 787 | } catch (\Exception $e) { |
868 | daniel-mar | 788 | return null; |
789 | } |
||
790 | return $objKey; |
||
791 | } |
||
792 | } |
||
793 | return null; |
||
794 | } |
||
795 | |||
796 | /** |
||
797 | * Returns: |
||
798 | * Bool when verifying HMAC_SHA1; |
||
799 | * Int otherwise, with following meanings: |
||
800 | * 1 on succesful signature verification, |
||
801 | * 0 when signature verification failed, |
||
802 | * -1 if an error occurred during processing. |
||
803 | * |
||
804 | * NOTE: be very careful when checking the int return value, because in |
||
805 | * PHP, -1 will be cast to True when in boolean context. Always check the |
||
806 | * return value in a strictly typed way, e.g. "$obj->verify(...) === 1". |
||
807 | * |
||
808 | * @param XMLSecurityKey $objKey |
||
809 | * @return bool|int |
||
810 | * @throws Exception |
||
811 | */ |
||
812 | public function verify($objKey) |
||
813 | { |
||
814 | $doc = $this->sigNode->ownerDocument; |
||
815 | $xpath = new DOMXPath($doc); |
||
816 | $xpath->registerNamespace('secdsig', self::XMLDSIGNS); |
||
817 | $query = "string(./secdsig:SignatureValue)"; |
||
818 | $sigValue = $xpath->evaluate($query, $this->sigNode); |
||
819 | if (empty($sigValue)) { |
||
820 | throw new Exception("Unable to locate SignatureValue"); |
||
821 | } |
||
822 | return $objKey->verifySignature($this->signedInfo, base64_decode($sigValue)); |
||
823 | } |
||
824 | |||
825 | /** |
||
826 | * @param XMLSecurityKey $objKey |
||
827 | * @param string $data |
||
828 | * @return mixed|string |
||
829 | */ |
||
830 | public function signData($objKey, $data) |
||
831 | { |
||
832 | return $objKey->signData($data); |
||
833 | } |
||
834 | |||
835 | /** |
||
836 | * @param XMLSecurityKey $objKey |
||
837 | * @param null|DOMNode $appendToNode |
||
838 | */ |
||
839 | public function sign($objKey, $appendToNode = null) |
||
840 | { |
||
841 | // If we have a parent node append it now so C14N properly works |
||
842 | if ($appendToNode != null) { |
||
843 | $this->resetXPathObj(); |
||
844 | $this->appendSignature($appendToNode); |
||
845 | $this->sigNode = $appendToNode->lastChild; |
||
846 | } |
||
847 | if ($xpath = $this->getXPathObj()) { |
||
848 | $query = "./secdsig:SignedInfo"; |
||
849 | $nodeset = $xpath->query($query, $this->sigNode); |
||
850 | if ($sInfo = $nodeset->item(0)) { |
||
851 | $query = "./secdsig:SignatureMethod"; |
||
852 | $nodeset = $xpath->query($query, $sInfo); |
||
853 | $sMethod = $nodeset->item(0); |
||
854 | $sMethod->setAttribute('Algorithm', $objKey->type); |
||
855 | $data = $this->canonicalizeData($sInfo, $this->canonicalMethod); |
||
856 | $sigValue = base64_encode($this->signData($objKey, $data)); |
||
857 | $sigValueNode = $this->createNewSignNode('SignatureValue', $sigValue); |
||
858 | if ($infoSibling = $sInfo->nextSibling) { |
||
859 | $infoSibling->parentNode->insertBefore($sigValueNode, $infoSibling); |
||
860 | } else { |
||
861 | $this->sigNode->appendChild($sigValueNode); |
||
862 | } |
||
863 | } |
||
864 | } |
||
865 | } |
||
866 | |||
867 | public function appendCert() |
||
868 | { |
||
869 | |||
870 | } |
||
871 | |||
872 | /** |
||
873 | * @param XMLSecurityKey $objKey |
||
874 | * @param null|DOMNode $parent |
||
875 | */ |
||
876 | public function appendKey($objKey, $parent=null) |
||
877 | { |
||
878 | $objKey->serializeKey($parent); |
||
879 | } |
||
880 | |||
881 | |||
882 | /** |
||
883 | * This function inserts the signature element. |
||
884 | * |
||
885 | * The signature element will be appended to the element, unless $beforeNode is specified. If $beforeNode |
||
886 | * is specified, the signature element will be inserted as the last element before $beforeNode. |
||
887 | * |
||
888 | * @param DOMNode $node The node the signature element should be inserted into. |
||
889 | * @param DOMNode $beforeNode The node the signature element should be located before. |
||
890 | * |
||
891 | * @return DOMNode The signature element node |
||
892 | */ |
||
893 | public function insertSignature($node, $beforeNode = null) |
||
894 | { |
||
895 | |||
896 | $document = $node->ownerDocument; |
||
897 | $signatureElement = $document->importNode($this->sigNode, true); |
||
898 | |||
899 | if ($beforeNode == null) { |
||
900 | return $node->insertBefore($signatureElement); |
||
901 | } else { |
||
902 | return $node->insertBefore($signatureElement, $beforeNode); |
||
903 | } |
||
904 | } |
||
905 | |||
906 | /** |
||
907 | * @param DOMNode $parentNode |
||
908 | * @param bool $insertBefore |
||
909 | * @return DOMNode |
||
910 | */ |
||
911 | public function appendSignature($parentNode, $insertBefore = false) |
||
912 | { |
||
913 | $beforeNode = $insertBefore ? $parentNode->firstChild : null; |
||
914 | return $this->insertSignature($parentNode, $beforeNode); |
||
915 | } |
||
916 | |||
917 | /** |
||
918 | * @param string $cert |
||
919 | * @param bool $isPEMFormat |
||
920 | * @return string |
||
921 | */ |
||
922 | public static function get509XCert($cert, $isPEMFormat=true) |
||
923 | { |
||
924 | $certs = self::staticGet509XCerts($cert, $isPEMFormat); |
||
925 | if (! empty($certs)) { |
||
926 | return $certs[0]; |
||
927 | } |
||
928 | return ''; |
||
929 | } |
||
930 | |||
931 | /** |
||
932 | * @param string $certs |
||
933 | * @param bool $isPEMFormat |
||
934 | * @return array |
||
935 | */ |
||
936 | public static function staticGet509XCerts($certs, $isPEMFormat=true) |
||
937 | { |
||
938 | if ($isPEMFormat) { |
||
939 | $data = ''; |
||
940 | $certlist = array(); |
||
941 | $arCert = explode("\n", $certs); |
||
942 | $inData = false; |
||
943 | foreach ($arCert AS $curData) { |
||
944 | if (! $inData) { |
||
945 | if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) { |
||
946 | $inData = true; |
||
947 | } |
||
948 | } else { |
||
949 | if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) { |
||
950 | $inData = false; |
||
951 | $certlist[] = $data; |
||
952 | $data = ''; |
||
953 | continue; |
||
954 | } |
||
955 | $data .= trim($curData); |
||
956 | } |
||
957 | } |
||
958 | return $certlist; |
||
959 | } else { |
||
960 | return array($certs); |
||
961 | } |
||
962 | } |
||
963 | |||
964 | /** |
||
965 | * @param DOMElement $parentRef |
||
966 | * @param string $cert |
||
967 | * @param bool $isPEMFormat |
||
968 | * @param bool $isURL |
||
969 | * @param null|DOMXPath $xpath |
||
970 | * @param null|array $options |
||
971 | * @throws Exception |
||
972 | */ |
||
973 | public static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=true, $isURL=false, $xpath=null, $options=null) |
||
974 | { |
||
975 | if ($isURL) { |
||
976 | $cert = file_get_contents($cert); |
||
977 | } |
||
978 | if (! $parentRef instanceof DOMElement) { |
||
979 | throw new Exception('Invalid parent Node parameter'); |
||
980 | } |
||
981 | $baseDoc = $parentRef->ownerDocument; |
||
982 | |||
983 | if (empty($xpath)) { |
||
984 | $xpath = new DOMXPath($parentRef->ownerDocument); |
||
985 | $xpath->registerNamespace('secdsig', self::XMLDSIGNS); |
||
986 | } |
||
987 | |||
988 | $query = "./secdsig:KeyInfo"; |
||
989 | $nodeset = $xpath->query($query, $parentRef); |
||
990 | $keyInfo = $nodeset->item(0); |
||
991 | $dsig_pfx = ''; |
||
992 | if (! $keyInfo) { |
||
993 | $pfx = $parentRef->lookupPrefix(self::XMLDSIGNS); |
||
994 | if (! empty($pfx)) { |
||
995 | $dsig_pfx = $pfx.":"; |
||
996 | } |
||
997 | $inserted = false; |
||
998 | $keyInfo = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'KeyInfo'); |
||
999 | |||
1000 | $query = "./secdsig:Object"; |
||
1001 | $nodeset = $xpath->query($query, $parentRef); |
||
1002 | if ($sObject = $nodeset->item(0)) { |
||
1003 | $sObject->parentNode->insertBefore($keyInfo, $sObject); |
||
1004 | $inserted = true; |
||
1005 | } |
||
1006 | |||
1007 | if (! $inserted) { |
||
1008 | $parentRef->appendChild($keyInfo); |
||
1009 | } |
||
1010 | } else { |
||
1011 | $pfx = $keyInfo->lookupPrefix(self::XMLDSIGNS); |
||
1012 | if (! empty($pfx)) { |
||
1013 | $dsig_pfx = $pfx.":"; |
||
1014 | } |
||
1015 | } |
||
1016 | |||
1017 | // Add all certs if there are more than one |
||
1018 | $certs = self::staticGet509XCerts($cert, $isPEMFormat); |
||
1019 | |||
1020 | // Attach X509 data node |
||
1021 | $x509DataNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509Data'); |
||
1022 | $keyInfo->appendChild($x509DataNode); |
||
1023 | |||
1024 | $issuerSerial = false; |
||
1025 | $subjectName = false; |
||
1026 | if (is_array($options)) { |
||
1027 | if (! empty($options['issuerSerial'])) { |
||
1028 | $issuerSerial = true; |
||
1029 | } |
||
1030 | if (! empty($options['subjectName'])) { |
||
1031 | $subjectName = true; |
||
1032 | } |
||
1033 | } |
||
1034 | |||
1035 | // Attach all certificate nodes and any additional data |
||
1036 | foreach ($certs as $X509Cert) { |
||
1037 | if ($issuerSerial || $subjectName) { |
||
1038 | if ($certData = openssl_x509_parse("-----BEGIN CERTIFICATE-----\n".chunk_split($X509Cert, 64, "\n")."-----END CERTIFICATE-----\n")) { |
||
1039 | if ($subjectName && ! empty($certData['subject'])) { |
||
1040 | if (is_array($certData['subject'])) { |
||
1041 | $parts = array(); |
||
1042 | foreach ($certData['subject'] AS $key => $value) { |
||
1043 | if (is_array($value)) { |
||
1044 | foreach ($value as $valueElement) { |
||
1045 | array_unshift($parts, "$key=$valueElement"); |
||
1046 | } |
||
1047 | } else { |
||
1048 | array_unshift($parts, "$key=$value"); |
||
1049 | } |
||
1050 | } |
||
1051 | $subjectNameValue = implode(',', $parts); |
||
1052 | } else { |
||
1053 | $subjectNameValue = $certData['issuer']; |
||
1054 | } |
||
1055 | $x509SubjectNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509SubjectName', $subjectNameValue); |
||
1056 | $x509DataNode->appendChild($x509SubjectNode); |
||
1057 | } |
||
1058 | if ($issuerSerial && ! empty($certData['issuer']) && ! empty($certData['serialNumber'])) { |
||
1059 | if (is_array($certData['issuer'])) { |
||
1060 | $parts = array(); |
||
1061 | foreach ($certData['issuer'] AS $key => $value) { |
||
1062 | array_unshift($parts, "$key=$value"); |
||
1063 | } |
||
1064 | $issuerName = implode(',', $parts); |
||
1065 | } else { |
||
1066 | $issuerName = $certData['issuer']; |
||
1067 | } |
||
1068 | |||
1069 | $x509IssuerNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509IssuerSerial'); |
||
1070 | $x509DataNode->appendChild($x509IssuerNode); |
||
1071 | |||
1072 | $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509IssuerName', $issuerName); |
||
1073 | $x509IssuerNode->appendChild($x509Node); |
||
1074 | $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509SerialNumber', $certData['serialNumber']); |
||
1075 | $x509IssuerNode->appendChild($x509Node); |
||
1076 | } |
||
1077 | } |
||
1078 | |||
1079 | } |
||
1080 | $x509CertNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509Certificate', $X509Cert); |
||
1081 | $x509DataNode->appendChild($x509CertNode); |
||
1082 | } |
||
1083 | } |
||
1084 | |||
1085 | /** |
||
1086 | * @param string $cert |
||
1087 | * @param bool $isPEMFormat |
||
1088 | * @param bool $isURL |
||
1089 | * @param null|array $options |
||
1090 | */ |
||
1091 | public function add509Cert($cert, $isPEMFormat=true, $isURL=false, $options=null) |
||
1092 | { |
||
1093 | if ($xpath = $this->getXPathObj()) { |
||
1094 | self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath, $options); |
||
1095 | } |
||
1096 | } |
||
1097 | |||
1098 | /** |
||
1099 | * This function appends a node to the KeyInfo. |
||
1100 | * |
||
1101 | * The KeyInfo element will be created if one does not exist in the document. |
||
1102 | * |
||
1103 | * @param DOMNode $node The node to append to the KeyInfo. |
||
1104 | * |
||
1105 | * @return DOMNode The KeyInfo element node |
||
1106 | */ |
||
1107 | public function appendToKeyInfo($node) |
||
1108 | { |
||
1109 | $parentRef = $this->sigNode; |
||
1110 | $baseDoc = $parentRef->ownerDocument; |
||
1111 | |||
1112 | $xpath = $this->getXPathObj(); |
||
1113 | if (empty($xpath)) { |
||
1114 | $xpath = new DOMXPath($parentRef->ownerDocument); |
||
1115 | $xpath->registerNamespace('secdsig', self::XMLDSIGNS); |
||
1116 | } |
||
1117 | |||
1118 | $query = "./secdsig:KeyInfo"; |
||
1119 | $nodeset = $xpath->query($query, $parentRef); |
||
1120 | $keyInfo = $nodeset->item(0); |
||
1121 | if (! $keyInfo) { |
||
1122 | $dsig_pfx = ''; |
||
1123 | $pfx = $parentRef->lookupPrefix(self::XMLDSIGNS); |
||
1124 | if (! empty($pfx)) { |
||
1125 | $dsig_pfx = $pfx.":"; |
||
1126 | } |
||
1127 | $inserted = false; |
||
1128 | $keyInfo = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'KeyInfo'); |
||
1129 | |||
1130 | $query = "./secdsig:Object"; |
||
1131 | $nodeset = $xpath->query($query, $parentRef); |
||
1132 | if ($sObject = $nodeset->item(0)) { |
||
1133 | $sObject->parentNode->insertBefore($keyInfo, $sObject); |
||
1134 | $inserted = true; |
||
1135 | } |
||
1136 | |||
1137 | if (! $inserted) { |
||
1138 | $parentRef->appendChild($keyInfo); |
||
1139 | } |
||
1140 | } |
||
1141 | |||
1142 | $keyInfo->appendChild($node); |
||
1143 | |||
1144 | return $keyInfo; |
||
1145 | } |
||
1146 | |||
1147 | /** |
||
1148 | * This function retrieves an associative array of the validated nodes. |
||
1149 | * |
||
1150 | * The array will contain the id of the referenced node as the key and the node itself |
||
1151 | * as the value. |
||
1152 | * |
||
1153 | * Returns: |
||
1154 | * An associative array of validated nodes or null if no nodes have been validated. |
||
1155 | * |
||
1156 | * @return array Associative array of validated nodes |
||
1157 | */ |
||
1158 | public function getValidatedNodes() |
||
1159 | { |
||
1160 | return $this->validatedNodes; |
||
1161 | } |
||
1162 | } |