]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/Users/authentication/SAMLAuthenticate/lib/xmlseclibs/xmlseclibs.php
Release 6.5.0
[Github/sugarcrm.git] / modules / Users / authentication / SAMLAuthenticate / lib / xmlseclibs / xmlseclibs.php
1 <?php
2 /**
3  * xmlseclibs.php
4  *
5  * Copyright (c) 2007, Robert Richards <rrichards@cdatazone.org>.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  *   * Redistributions of source code must retain the above copyright
13  *     notice, this list of conditions and the following disclaimer.
14  *
15  *   * Redistributions in binary form must reproduce the above copyright
16  *     notice, this list of conditions and the following disclaimer in
17  *     the documentation and/or other materials provided with the
18  *     distribution.
19  *
20  *   * Neither the name of Robert Richards nor the names of his
21  *     contributors may be used to endorse or promote products derived
22  *     from this software without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35  * POSSIBILITY OF SUCH DAMAGE.
36  *
37  * @author     Robert Richards <rrichards@cdatazone.org>
38  * @copyright  2007 Robert Richards <rrichards@cdatazone.org>
39  * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License
40  * @version    1.2.2
41  */
42
43 /*
44 Functions to generate simple cases of Exclusive Canonical XML - Callable function is C14NGeneral()
45 i.e.: $canonical = C14NGeneral($domelement, TRUE);
46 */
47
48 /* helper function */
49 function sortAndAddAttrs($element, $arAtts) {
50    $newAtts = array();
51    foreach ($arAtts AS $attnode) {
52       $newAtts[$attnode->nodeName] = $attnode;
53    }
54    ksort($newAtts);
55    foreach ($newAtts as $attnode) {
56       $element->setAttribute($attnode->nodeName, $attnode->nodeValue);
57    }
58 }
59
60 /* helper function */
61 function canonical($tree, $element, $withcomments) {
62     if ($tree->nodeType != XML_DOCUMENT_NODE) {
63         $dom = $tree->ownerDocument;
64     } else {
65         $dom = $tree;
66     }
67     if ($element->nodeType != XML_ELEMENT_NODE) {
68         if ($element->nodeType == XML_DOCUMENT_NODE) {
69             foreach ($element->childNodes AS $node) {
70                 canonical($dom, $node, $withcomments);
71             }
72             return;
73         }
74         if ($element->nodeType == XML_COMMENT_NODE && ! $withcomments) {
75             return;
76         }
77         $tree->appendChild($dom->importNode($element, TRUE));
78         return;
79     }
80     $arNS = array();
81     if ($element->namespaceURI != "") {
82         if ($element->prefix == "") {
83             $elCopy = $dom->createElementNS($element->namespaceURI, $element->nodeName);
84         } else {
85             $prefix = $tree->lookupPrefix($element->namespaceURI);
86             if ($prefix == $element->prefix) {
87                 $elCopy = $dom->createElementNS($element->namespaceURI, $element->nodeName);
88             } else {
89                 $elCopy = $dom->createElement($element->nodeName);
90                 $arNS[$element->namespaceURI] = $element->prefix;
91             }
92         }
93     } else {
94         $elCopy = $dom->createElement($element->nodeName);
95     }
96     $tree->appendChild($elCopy);
97
98     /* Create DOMXPath based on original document */
99     $xPath = new DOMXPath($element->ownerDocument);
100
101     /* Get namespaced attributes */
102     $arAtts = $xPath->query('attribute::*[namespace-uri(.) != ""]', $element);
103
104     /* Create an array with namespace URIs as keys, and sort them */
105     foreach ($arAtts AS $attnode) {
106         if (array_key_exists($attnode->namespaceURI, $arNS) &&
107             ($arNS[$attnode->namespaceURI] == $attnode->prefix)) {
108             continue;
109         }
110         $prefix = $tree->lookupPrefix($attnode->namespaceURI);
111         if ($prefix != $attnode->prefix) {
112            $arNS[$attnode->namespaceURI] = $attnode->prefix;
113         } else {
114             $arNS[$attnode->namespaceURI] = NULL;
115         }
116     }
117     if (count($arNS) > 0) {
118         asort($arNS);
119     }
120
121     /* Add namespace nodes */
122     foreach ($arNS AS $namespaceURI=>$prefix) {
123         if ($prefix != NULL) {
124               $elCopy->setAttributeNS("http://www.w3.org/2000/xmlns/",
125                                "xmlns:".$prefix, $namespaceURI);
126         }
127     }
128     if (count($arNS) > 0) {
129         ksort($arNS);
130     }
131
132     /* Get attributes not in a namespace, and then sort and add them */
133     $arAtts = $xPath->query('attribute::*[namespace-uri(.) = ""]', $element);
134     sortAndAddAttrs($elCopy, $arAtts);
135
136     /* Loop through the URIs, and then sort and add attributes within that namespace */
137     foreach ($arNS as $nsURI=>$prefix) {
138        $arAtts = $xPath->query('attribute::*[namespace-uri(.) = "'.$nsURI.'"]', $element);
139        sortAndAddAttrs($elCopy, $arAtts);
140     }
141
142     foreach ($element->childNodes AS $node) {
143         canonical($elCopy, $node, $withcomments);
144     }
145 }
146
147 /*
148 $element - DOMElement for which to produce the canonical version of
149 $exclusive - boolean to indicate exclusive canonicalization (must pass TRUE)
150 $withcomments - boolean indicating wether or not to include comments in canonicalized form
151 */
152 function C14NGeneral($element, $exclusive=FALSE, $withcomments=FALSE) {
153     /* IF PHP 5.2+ then use built in canonical functionality */
154     $php_version = explode('.', PHP_VERSION);
155     if (($php_version[0] > 5) || ($php_version[0] == 5 && $php_version[1] >= 2) ) {
156         return $element->C14N($exclusive, $withcomments);
157     }
158
159     /* Must be element or document */
160     if (! $element instanceof DOMElement && ! $element instanceof DOMDocument) {
161         return NULL;
162     }
163     /* Currently only exclusive XML is supported */
164     if ($exclusive == FALSE) {
165         throw new Exception("Only exclusive canonicalization is supported in this version of PHP");
166     }
167
168     $copyDoc = new DOMDocument();
169     canonical($copyDoc, $element, $withcomments);
170     return $copyDoc->saveXML($copyDoc->documentElement, LIBXML_NOEMPTYTAG);
171 }
172
173 class XMLSecurityKey {
174     const TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
175     const AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
176     const AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
177     const AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
178     const RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
179     const RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
180     const DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1';
181     const RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
182     const RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
183
184     private $cryptParams = array();
185     public $type = 0;
186     public $key = NULL;
187     public $passphrase = "";
188     public $iv = NULL;
189     public $name = NULL;
190     public $keyChain = NULL;
191     public $isEncrypted = FALSE;
192     public $encryptedCtx = NULL;
193     public $guid = NULL;
194
195     /**
196      * This variable contains the certificate as a string if this key represents an X509-certificate.
197      * If this key doesn't represent a certificate, this will be NULL.
198      */
199     private $x509Certificate = NULL;
200
201     public function __construct($type, $params=NULL) {
202         srand();
203         switch ($type) {
204             case (XMLSecurityKey::TRIPLEDES_CBC):
205                 $this->cryptParams['library'] = 'mcrypt';
206                 $this->cryptParams['cipher'] = MCRYPT_TRIPLEDES;
207                 $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
208                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
209                 break;
210             case (XMLSecurityKey::AES128_CBC):
211                 $this->cryptParams['library'] = 'mcrypt';
212                 $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
213                 $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
214                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
215                 break;
216             case (XMLSecurityKey::AES192_CBC):
217                 $this->cryptParams['library'] = 'mcrypt';
218                 $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
219                 $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
220                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
221                 break;
222             case (XMLSecurityKey::AES256_CBC):
223                 $this->cryptParams['library'] = 'mcrypt';
224                 $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
225                 $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
226                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
227                 break;
228             case (XMLSecurityKey::RSA_1_5):
229                 $this->cryptParams['library'] = 'openssl';
230                 $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
231                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
232                 if (is_array($params) && ! empty($params['type'])) {
233                     if ($params['type'] == 'public' || $params['type'] == 'private') {
234                         $this->cryptParams['type'] = $params['type'];
235                         break;
236                     }
237                 }
238                 throw new Exception('Certificate "type" (private/public) must be passed via parameters');
239                 return;
240             case (XMLSecurityKey::RSA_OAEP_MGF1P):
241                 $this->cryptParams['library'] = 'openssl';
242                 $this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING;
243                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
244                 $this->cryptParams['hash'] = NULL;
245                 if (is_array($params) && ! empty($params['type'])) {
246                     if ($params['type'] == 'public' || $params['type'] == 'private') {
247                         $this->cryptParams['type'] = $params['type'];
248                         break;
249                     }
250                 }
251                 throw new Exception('Certificate "type" (private/public) must be passed via parameters');
252                 return;
253             case (XMLSecurityKey::RSA_SHA1):
254                 $this->cryptParams['library'] = 'openssl';
255                 $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
256                 $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
257                 if (is_array($params) && ! empty($params['type'])) {
258                     if ($params['type'] == 'public' || $params['type'] == 'private') {
259                         $this->cryptParams['type'] = $params['type'];
260                         break;
261                     }
262                 }
263                 throw new Exception('Certificate "type" (private/public) must be passed via parameters');
264                 break;
265             case (XMLSecurityKey::RSA_SHA256):
266                 $this->cryptParams['library'] = 'openssl';
267                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
268                 $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
269                 $this->cryptParams['digest'] = 'SHA256';
270                 if (is_array($params) && ! empty($params['type'])) {
271                     if ($params['type'] == 'public' || $params['type'] == 'private') {
272                         $this->cryptParams['type'] = $params['type'];
273                         break;
274                     }
275                 }
276                 throw new Exception('Certificate "type" (private/public) must be passed via parameters');
277                 break;
278             default:
279                 throw new Exception('Invalid Key Type');
280                 return;
281         }
282         $this->type = $type;
283     }
284
285     public function generateSessionKey() {
286         $key = '';
287         if (! empty($this->cryptParams['cipher']) && ! empty($this->cryptParams['mode'])) {
288             $keysize = mcrypt_module_get_algo_key_size($this->cryptParams['cipher']);
289             /* Generating random key using iv generation routines */
290             if (($keysize > 0) && ($td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '',$this->cryptParams['mode'], ''))) {
291                 if ($this->cryptParams['cipher'] == MCRYPT_RIJNDAEL_128) {
292                     $keysize = 16;
293                     if ($this->type == XMLSecurityKey::AES256_CBC) {
294                         $keysize = 32;
295                     } elseif ($this->type == XMLSecurityKey::AES192_CBC) {
296                         $keysize = 24;
297                     }
298                 }
299                 while (strlen($key) < $keysize) {
300                     $key .= mcrypt_create_iv(mcrypt_enc_get_iv_size ($td),MCRYPT_RAND);
301                 }
302                 mcrypt_module_close($td);
303                 $key = substr($key, 0, $keysize);
304                 $this->key = $key;
305             }
306         }
307         return $key;
308     }
309
310     public function loadKey($key, $isFile=FALSE, $isCert = FALSE) {
311         if ($isFile) {
312             $this->key = file_get_contents($key);
313         } else {
314             $this->key = $key;
315         }
316         if ($isCert) {
317             $this->key = openssl_x509_read($this->key);
318             openssl_x509_export($this->key, $str_cert);
319             $this->x509Certificate = $str_cert;
320             $this->key = $str_cert;
321         } else {
322             $this->x509Certificate = NULL;
323         }
324         if ($this->cryptParams['library'] == 'openssl') {
325             if ($this->cryptParams['type'] == 'public') {
326                 $this->key = openssl_get_publickey($this->key);
327             } else {
328                 $this->key = openssl_get_privatekey($this->key, $this->passphrase);
329             }
330         } else if ($this->cryptParams['cipher'] == MCRYPT_RIJNDAEL_128) {
331             /* Check key length */
332             switch ($this->type) {
333                 case (XMLSecurityKey::AES256_CBC):
334                     if (strlen($this->key) < 25) {
335                         throw new Exception('Key must contain at least 25 characters for this cipher');
336                     }
337                     break;
338                 case (XMLSecurityKey::AES192_CBC):
339                     if (strlen($this->key) < 17) {
340                         throw new Exception('Key must contain at least 17 characters for this cipher');
341                     }
342                     break;
343             }
344         }
345     }
346
347     private function encryptMcrypt($data) {
348         $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
349         $this->iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
350         mcrypt_generic_init($td, $this->key, $this->iv);
351         if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
352             $bs = mcrypt_enc_get_block_size($td);
353             for ($datalen0=$datalen=strlen($data); (($datalen%$bs)!=($bs-1)); $datalen++)
354                 $data.=chr(rand(1, 127));
355             $data.=chr($datalen-$datalen0+1);
356         }
357         $encrypted_data = $this->iv.mcrypt_generic($td, $data);
358         mcrypt_generic_deinit($td);
359         mcrypt_module_close($td);
360         return $encrypted_data;
361     }
362
363     private function decryptMcrypt($data) {
364         $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
365         $iv_length = mcrypt_enc_get_iv_size($td);
366
367         $this->iv = substr($data, 0, $iv_length);
368         $data = substr($data, $iv_length);
369
370         mcrypt_generic_init($td, $this->key, $this->iv);
371         $decrypted_data = mdecrypt_generic($td, $data);
372         mcrypt_generic_deinit($td);
373         mcrypt_module_close($td);
374         if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
375             $dataLen = strlen($decrypted_data);
376             $paddingLength = substr($decrypted_data, $dataLen - 1, 1);
377             $decrypted_data = substr($decrypted_data, 0, $dataLen - ord($paddingLength));
378         }
379         return $decrypted_data;
380     }
381
382     private function encryptOpenSSL($data) {
383         if ($this->cryptParams['type'] == 'public') {
384             if (! openssl_public_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
385                 throw new Exception('Failure encrypting Data');
386                 return;
387             }
388         } else {
389             if (! openssl_private_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
390                 throw new Exception('Failure encrypting Data');
391                 return;
392             }
393         }
394         return $encrypted_data;
395     }
396
397     private function decryptOpenSSL($data) {
398         if ($this->cryptParams['type'] == 'public') {
399             if (! openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
400                 throw new Exception('Failure decrypting Data');
401                 return;
402             }
403         } else {
404             if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
405                 throw new Exception('Failure decrypting Data');
406                 return;
407             }
408         }
409         return $decrypted;
410     }
411
412     private function signOpenSSL($data) {
413             $algo = OPENSSL_ALGO_SHA1;
414             if (! empty($this->cryptParams['digest'])) {
415                 $algo = $this->cryptParams['digest'];
416             }
417         if (! openssl_sign ($data, $signature, $this->key, $algo)) {
418             throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo);
419             return;
420         }
421         return $signature;
422     }
423
424     private function verifyOpenSSL($data, $signature) {
425             $algo = OPENSSL_ALGO_SHA1;
426             if (! empty($this->cryptParams['digest'])) {
427                 $algo = $this->cryptParams['digest'];
428             }
429         return openssl_verify ($data, $signature, $this->key, $algo);
430     }
431
432     public function encryptData($data) {
433         switch ($this->cryptParams['library']) {
434             case 'mcrypt':
435                 return $this->encryptMcrypt($data);
436                 break;
437             case 'openssl':
438                 return $this->encryptOpenSSL($data);
439                 break;
440         }
441     }
442
443     public function decryptData($data) {
444         switch ($this->cryptParams['library']) {
445             case 'mcrypt':
446                 return $this->decryptMcrypt($data);
447                 break;
448             case 'openssl':
449                 return $this->decryptOpenSSL($data);
450                 break;
451         }
452     }
453
454     public function signData($data) {
455         switch ($this->cryptParams['library']) {
456             case 'openssl':
457                 return $this->signOpenSSL($data);
458                 break;
459         }
460     }
461
462     public function verifySignature($data, $signature) {
463         switch ($this->cryptParams['library']) {
464             case 'openssl':
465                 return $this->verifyOpenSSL($data, $signature);
466                 break;
467         }
468     }
469
470     public function getAlgorith() {
471         return $this->cryptParams['method'];
472     }
473
474     static function makeAsnSegment($type, $string) {
475         switch ($type){
476             case 0x02:
477                 if (ord($string) > 0x7f)
478                     $string = chr(0).$string;
479                 break;
480             case 0x03:
481                 $string = chr(0).$string;
482                 break;
483         }
484
485         $length = strlen($string);
486
487         if ($length < 128){
488            $output = sprintf("%c%c%s", $type, $length, $string);
489         } else if ($length < 0x0100){
490            $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string);
491         } else if ($length < 0x010000) {
492            $output = sprintf("%c%c%c%c%s", $type, 0x82, $length/0x0100, $length%0x0100, $string);
493         } else {
494             $output = NULL;
495         }
496         return($output);
497     }
498
499     /* Modulus and Exponent must already be base64 decoded */
500     static function convertRSA($modulus, $exponent) {
501         /* make an ASN publicKeyInfo */
502         $exponentEncoding = XMLSecurityKey::makeAsnSegment(0x02, $exponent);
503         $modulusEncoding = XMLSecurityKey::makeAsnSegment(0x02, $modulus);
504         $sequenceEncoding = XMLSecurityKey:: makeAsnSegment(0x30, $modulusEncoding.$exponentEncoding);
505         $bitstringEncoding = XMLSecurityKey::makeAsnSegment(0x03, $sequenceEncoding);
506         $rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500");
507         $publicKeyInfo = XMLSecurityKey::makeAsnSegment (0x30, $rsaAlgorithmIdentifier.$bitstringEncoding);
508
509         /* encode the publicKeyInfo in base64 and add PEM brackets */
510         $publicKeyInfoBase64 = base64_encode($publicKeyInfo);
511         $encoding = "-----BEGIN PUBLIC KEY-----\n";
512         $offset = 0;
513         while ($segment=substr($publicKeyInfoBase64, $offset, 64)){
514            $encoding = $encoding.$segment."\n";
515            $offset += 64;
516         }
517         return $encoding."-----END PUBLIC KEY-----\n";
518     }
519
520     public function serializeKey($parent) {
521
522     }
523     
524
525
526     /**
527      * Retrieve the X509 certificate this key represents.
528      *
529      * Will return the X509 certificate in PEM-format if this key represents
530      * an X509 certificate.
531      *
532      * @return  The X509 certificate or NULL if this key doesn't represent an X509-certificate.
533      */
534     public function getX509Certificate() {
535         return $this->x509Certificate;
536     }
537     
538 }
539
540 class XMLSecurityDSig {
541     const XMLDSIGNS = 'http://www.w3.org/2000/09/xmldsig#';
542     const SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1';
543     const SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256';
544     const SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512';
545     const RIPEMD160 = 'http://www.w3.org/2001/04/xmlenc#ripemd160';
546
547     const C14N = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
548     const C14N_COMMENTS = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments';
549     const EXC_C14N = 'http://www.w3.org/2001/10/xml-exc-c14n#';
550     const EXC_C14N_COMMENTS = 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments';
551
552     const template = '<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
553   <ds:SignedInfo>
554     <ds:SignatureMethod />
555   </ds:SignedInfo>
556 </ds:Signature>';
557
558     public $sigNode = NULL;
559     public $idKeys = array();
560     public $idNS = array();
561     private $signedInfo = NULL;
562     private $xPathCtx = NULL;
563     private $canonicalMethod = NULL;
564     private $prefix = 'ds';
565     private $searchpfx = 'secdsig';
566
567     /* This variable contains an associative array of validated nodes. */
568     private $validatedNodes = NULL;
569
570     public function __construct() {
571         $sigdoc = new DOMDocument();
572         $sigdoc->loadXML(XMLSecurityDSig::template);
573         $this->sigNode = $sigdoc->documentElement;
574     }
575
576     private function getXPathObj() {
577         if (empty($this->xPathCtx) && ! empty($this->sigNode)) {
578             $xpath = new DOMXPath($this->sigNode->ownerDocument);
579             $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
580             $this->xPathCtx = $xpath;
581         }
582         return $this->xPathCtx;
583     }
584
585     static function generate_GUID($prefix='pfx') {
586         $uuid = md5(uniqid(rand(), true));
587         $guid =  $prefix.substr($uuid,0,8)."-".
588                 substr($uuid,8,4)."-".
589                 substr($uuid,12,4)."-".
590                 substr($uuid,16,4)."-".
591                 substr($uuid,20,12);
592         return $guid;
593     }
594
595     public function locateSignature($objDoc) {
596         if ($objDoc instanceof DOMDocument) {
597             $doc = $objDoc;
598         } else {
599             $doc = $objDoc->ownerDocument;
600         }
601         if ($doc) {
602             $xpath = new DOMXPath($doc);
603             $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
604             $query = ".//secdsig:Signature";
605             $nodeset = $xpath->query($query, $objDoc);
606             $this->sigNode = $nodeset->item(0);
607             return $this->sigNode;
608         }
609         return NULL;
610     }
611
612     public function createNewSignNode($name, $value=NULL) {
613         $doc = $this->sigNode->ownerDocument;
614         if (! is_null($value)) {
615             $node = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, $this->prefix.':'.$name, $value);
616         } else {
617             $node = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, $this->prefix.':'.$name);
618         }
619         return $node;
620     }
621
622     public function setCanonicalMethod($method) {
623         switch ($method) {
624             case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
625             case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
626             case 'http://www.w3.org/2001/10/xml-exc-c14n#':
627             case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
628                 $this->canonicalMethod = $method;
629                 break;
630             default:
631                 throw new Exception('Invalid Canonical Method');
632         }
633         if ($xpath = $this->getXPathObj()) {
634             $query = './'.$this->searchpfx.':SignedInfo';
635             $nodeset = $xpath->query($query, $this->sigNode);
636             if ($sinfo = $nodeset->item(0)) {
637                 $query = './'.$this->searchpfx.'CanonicalizationMethod';
638                 $nodeset = $xpath->query($query, $sinfo);
639                 if (! ($canonNode = $nodeset->item(0))) {
640                     $canonNode = $this->createNewSignNode('CanonicalizationMethod');
641                     $sinfo->insertBefore($canonNode, $sinfo->firstChild);
642                 }
643                 $canonNode->setAttribute('Algorithm', $this->canonicalMethod);
644             }
645         }
646     }
647
648     private function canonicalizeData($node, $canonicalmethod, $arXPath=NULL, $prefixList=NULL) {
649         $exclusive = FALSE;
650         $withComments = FALSE;
651         switch ($canonicalmethod) {
652             case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
653                 $exclusive = FALSE;
654                 $withComments = FALSE;
655                 break;
656             case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
657                 $withComments = TRUE;
658                 break;
659             case 'http://www.w3.org/2001/10/xml-exc-c14n#':
660                 $exclusive = TRUE;
661                 break;
662             case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
663                 $exclusive = TRUE;
664                 $withComments = TRUE;
665                 break;
666         }
667 /* Support PHP versions < 5.2 not containing C14N methods in DOM extension */
668         $php_version = explode('.', PHP_VERSION);
669         if (($php_version[0] < 5) || ($php_version[0] == 5 && $php_version[1] < 2) ) {
670             if (! is_null($arXPath)) {
671                 throw new Exception("PHP 5.2.0 or higher is required to perform XPath Transformations");
672             }
673             return C14NGeneral($node, $exclusive, $withComments);
674         }
675         return $node->C14N($exclusive, $withComments, $arXPath, $prefixList);
676     }
677
678     public function canonicalizeSignedInfo() {
679
680         $doc = $this->sigNode->ownerDocument;
681         $canonicalmethod = NULL;
682         if ($doc) {
683             $xpath = $this->getXPathObj();
684             $query = "./secdsig:SignedInfo";
685             $nodeset = $xpath->query($query, $this->sigNode);
686             if ($signInfoNode = $nodeset->item(0)) {
687                 $query = "./secdsig:CanonicalizationMethod";
688                 $nodeset = $xpath->query($query, $signInfoNode);
689                 if ($canonNode = $nodeset->item(0)) {
690                     $canonicalmethod = $canonNode->getAttribute('Algorithm');
691                 }
692                 $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod);
693                 return $this->signedInfo;
694             }
695         }
696         return NULL;
697     }
698
699     public function calculateDigest ($digestAlgorithm, $data) {
700         switch ($digestAlgorithm) {
701             case XMLSecurityDSig::SHA1:
702                 $alg = 'sha1';
703                 break;
704             case XMLSecurityDSig::SHA256:
705                 $alg = 'sha256';
706                 break;
707             case XMLSecurityDSig::SHA512:
708                 $alg = 'sha512';
709                 break;
710             case XMLSecurityDSig::RIPEMD160:
711                 $alg = 'ripemd160';
712                 break;
713             default:
714                 throw new Exception("Cannot validate digest: Unsupported Algorith <$digestAlgorithm>");
715         }
716         if (function_exists('hash')) {
717             return base64_encode(hash($alg, $data, TRUE));
718         } elseif (function_exists('mhash')) {
719             $alg = "MHASH_" . strtoupper($alg);
720             return base64_encode(mhash(constant($alg), $data));
721         } elseif ($alg === 'sha1') {
722             return base64_encode(sha1($data, TRUE));
723         } else {
724             throw new Exception('xmlseclibs is unable to calculate a digest. Maybe you need the mhash library?');
725         }
726     }
727
728     public function validateDigest($refNode, $data) {
729         $xpath = new DOMXPath($refNode->ownerDocument);
730         $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
731         $query = 'string(./secdsig:DigestMethod/@Algorithm)';
732         $digestAlgorithm = $xpath->evaluate($query, $refNode);
733         $digValue = $this->calculateDigest($digestAlgorithm, $data);
734         $query = 'string(./secdsig:DigestValue)';
735         $digestValue = $xpath->evaluate($query, $refNode);
736         return ($digValue == $digestValue);
737     }
738
739     public function processTransforms($refNode, $objData) {
740         $data = $objData;
741         $xpath = new DOMXPath($refNode->ownerDocument);
742         $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
743         $query = './secdsig:Transforms/secdsig:Transform';
744         $nodelist = $xpath->query($query, $refNode);
745         $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
746         $arXPath = NULL;
747         $prefixList = NULL;
748         foreach ($nodelist AS $transform) {
749             $algorithm = $transform->getAttribute("Algorithm");
750             switch ($algorithm) {
751                 case 'http://www.w3.org/2001/10/xml-exc-c14n#':
752                 case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
753                     $node = $transform->firstChild;
754                     while ($node) {
755                         if ($node->localName == 'InclusiveNamespaces') {
756                             if ($pfx = $node->getAttribute('PrefixList')) {
757                                 $arpfx = array();
758                                 $pfxlist = preg_split("/\s/", $pfx);
759                                 foreach ($pfxlist AS $pfx) {
760                                     $val = trim($pfx);
761                                     if (! empty($val)) {
762                                         $arpfx[] = $val;
763                                     }
764                                 }
765                                 if (count($arpfx) > 0) {
766                                     $prefixList = $arpfx;
767                                 }
768                             }
769                             break;
770                         }
771                         $node = $node->nextSibling;
772                     }
773                 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
774                 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
775                     $canonicalMethod = $algorithm;
776                     break;
777                 case 'http://www.w3.org/TR/1999/REC-xpath-19991116':
778                     $node = $transform->firstChild;
779                     while ($node) {
780                         if ($node->localName == 'XPath') {
781                             $arXPath = array();
782                             $arXPath['query'] = '(.//. | .//@* | .//namespace::*)['.$node->nodeValue.']';
783                             $arXpath['namespaces'] = array();
784                             $nslist = $xpath->query('./namespace::*', $node);
785                             foreach ($nslist AS $nsnode) {
786                                 if ($nsnode->localName != "xml") {
787                                     $arXPath['namespaces'][$nsnode->localName] = $nsnode->nodeValue;
788                                 }
789                             }
790                             break;
791                         }
792                         $node = $node->nextSibling;
793                     }
794                     break;
795             }
796         }
797         if ($data instanceof DOMNode) {
798             $data = $this->canonicalizeData($objData, $canonicalMethod, $arXPath, $prefixList);
799         }
800         return $data;
801     }
802
803     public function processRefNode($refNode) {
804         $dataObject = NULL;
805         if ($uri = $refNode->getAttribute("URI")) {
806             $arUrl = parse_url($uri);
807             if (empty($arUrl['path'])) {
808                 if ($identifier = $arUrl['fragment']) {
809                     $xPath = new DOMXPath($refNode->ownerDocument);
810                     if ($this->idNS && is_array($this->idNS)) {
811                         foreach ($this->idNS AS $nspf=>$ns) {
812                             $xPath->registerNamespace($nspf, $ns);
813                         }
814                     }
815                     $iDlist = '@Id="'.$identifier.'"';
816                     if (is_array($this->idKeys)) {
817                         foreach ($this->idKeys AS $idKey) {
818                             $iDlist .= " or @$idKey='$identifier'";
819                         }
820                     }
821                     $query = '//*['.$iDlist.']';
822                     $dataObject = $xPath->query($query)->item(0);
823                 } else {
824                     $dataObject = $refNode->ownerDocument;
825                 }
826             } else {
827                 $dataObject = file_get_contents($arUrl);
828             }
829         } else {
830             $dataObject = $refNode->ownerDocument;
831         }
832         $data = $this->processTransforms($refNode, $dataObject);
833         if (!$this->validateDigest($refNode, $data)) {
834             return FALSE;
835         }
836
837         if ($dataObject instanceof DOMNode) {
838             /* Add this node to the list of validated nodes. */
839             if(! empty($identifier)) {
840                 $this->validatedNodes[$identifier] = $dataObject;
841             } else {
842                 $this->validatedNodes[] = $dataObject;
843             }
844         }
845
846         return TRUE;
847     }
848
849     public function getRefNodeID($refNode) {
850         if ($uri = $refNode->getAttribute("URI")) {
851             $arUrl = parse_url($uri);
852             if (empty($arUrl['path'])) {
853                 if ($identifier = $arUrl['fragment']) {
854                     return $identifier;
855                 }
856             }
857         }
858         return null;
859     }
860
861     public function getRefIDs() {
862         $refids = array();
863         $doc = $this->sigNode->ownerDocument;
864
865         $xpath = $this->getXPathObj();
866         $query = "./secdsig:SignedInfo/secdsig:Reference";
867         $nodeset = $xpath->query($query, $this->sigNode);
868         if ($nodeset->length == 0) {
869             throw new Exception("Reference nodes not found");
870         }
871         foreach ($nodeset AS $refNode) {
872             $refids[] = $this->getRefNodeID($refNode);
873         }
874         return $refids;
875     }
876
877     public function validateReference() {
878         $doc = $this->sigNode->ownerDocument;
879         if (! $doc->isSameNode($this->sigNode)) {
880             $this->sigNode->parentNode->removeChild($this->sigNode);
881         }
882         $xpath = $this->getXPathObj();
883         $query = "./secdsig:SignedInfo/secdsig:Reference";
884         $nodeset = $xpath->query($query, $this->sigNode);
885         if ($nodeset->length == 0) {
886             throw new Exception("Reference nodes not found");
887         }
888         
889         /* Initialize/reset the list of validated nodes. */
890         $this->validatedNodes = array();
891         
892         foreach ($nodeset AS $refNode) {
893             if (! $this->processRefNode($refNode)) {
894                 /* Clear the list of validated nodes. */
895                 $this->validatedNodes = NULL;
896                 throw new Exception("Reference validation failed");
897             }
898         }
899         return TRUE;
900     }
901
902     private function addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=NULL, $options=NULL) {
903         $prefix = NULL;
904         $prefix_ns = NULL;
905         $id_name = 'Id';
906         $overwrite_id  = TRUE;
907         $force_uri = FALSE;
908
909         if (is_array($options)) {
910             $prefix = empty($options['prefix'])?NULL:$options['prefix'];
911             $prefix_ns = empty($options['prefix_ns'])?NULL:$options['prefix_ns'];
912             $id_name = empty($options['id_name'])?'Id':$options['id_name'];
913             $overwrite_id = !isset($options['overwrite'])?TRUE:(bool)$options['overwrite'];
914             $force_uri = !isset($options['force_uri'])?FALSE:(bool)$options['force_uri'];
915         }
916
917         $attname = $id_name;
918         if (! empty($prefix)) {
919             $attname = $prefix.':'.$attname;
920         }
921
922         $refNode = $this->createNewSignNode('Reference');
923         $sinfoNode->appendChild($refNode);
924
925         if (! $node instanceof DOMDocument) {
926             $uri = NULL;
927             if (! $overwrite_id) {
928                 $uri = $node->getAttributeNS($prefix_ns, $attname);
929             }
930             if (empty($uri)) {
931                 $uri = XMLSecurityDSig::generate_GUID();
932                 $node->setAttributeNS($prefix_ns, $attname, $uri);
933             }
934             $refNode->setAttribute("URI", '#'.$uri);
935         } elseif ($force_uri) {
936             $refNode->setAttribute("URI", '');
937         }
938
939         $transNodes = $this->createNewSignNode('Transforms');
940         $refNode->appendChild($transNodes);
941
942         if (is_array($arTransforms)) {
943             foreach ($arTransforms AS $transform) {
944                 $transNode = $this->createNewSignNode('Transform');
945                 $transNodes->appendChild($transNode);
946                 if (is_array($transform) && 
947                     (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116'])) && 
948                     (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']))) {
949                     $transNode->setAttribute('Algorithm', 'http://www.w3.org/TR/1999/REC-xpath-19991116');
950                     $XPathNode = $this->createNewSignNode('XPath', $transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']);
951                     $transNode->appendChild($XPathNode);
952                     if (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'])) {
953                         foreach ($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'] AS $prefix => $namespace) {
954                             $XPathNode->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:$prefix", $namespace);
955                         }
956                     }
957                 } else {
958                     $transNode->setAttribute('Algorithm', $transform);
959                 }
960             }
961         } elseif (! empty($this->canonicalMethod)) {
962             $transNode = $this->createNewSignNode('Transform');
963             $transNodes->appendChild($transNode);
964             $transNode->setAttribute('Algorithm', $this->canonicalMethod);
965         }
966
967         $canonicalData = $this->processTransforms($refNode, $node);
968         $digValue = $this->calculateDigest($algorithm, $canonicalData);
969
970         $digestMethod = $this->createNewSignNode('DigestMethod');
971         $refNode->appendChild($digestMethod);
972         $digestMethod->setAttribute('Algorithm', $algorithm);
973
974         $digestValue = $this->createNewSignNode('DigestValue', $digValue);
975         $refNode->appendChild($digestValue);
976     }
977
978     public function addReference($node, $algorithm, $arTransforms=NULL, $options=NULL) {
979         if ($xpath = $this->getXPathObj()) {
980             $query = "./secdsig:SignedInfo";
981             $nodeset = $xpath->query($query, $this->sigNode);
982             if ($sInfo = $nodeset->item(0)) {
983                 $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options);
984             }
985         }
986     }
987
988     public function addReferenceList($arNodes, $algorithm, $arTransforms=NULL, $options=NULL) {
989         if ($xpath = $this->getXPathObj()) {
990             $query = "./secdsig:SignedInfo";
991             $nodeset = $xpath->query($query, $this->sigNode);
992             if ($sInfo = $nodeset->item(0)) {
993                 foreach ($arNodes AS $node) {
994                     $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options);
995                 }
996             }
997         }
998     }
999
1000    public function addObject($data, $mimetype=NULL, $encoding=NULL) {
1001       $objNode = $this->createNewSignNode('Object');
1002       $this->sigNode->appendChild($objNode);
1003       if (! empty($mimetype)) {
1004          $objNode->setAtribute('MimeType', $mimetype);
1005       }
1006       if (! empty($encoding)) {
1007          $objNode->setAttribute('Encoding', $encoding);
1008       }
1009
1010       if ($data instanceof DOMElement) {
1011          $newData = $this->sigNode->ownerDocument->importNode($data, TRUE);
1012       } else {
1013          $newData = $this->sigNode->ownerDocument->createTextNode($data);
1014       }
1015       $objNode->appendChild($newData);
1016
1017       return $objNode;
1018    }
1019
1020     public function locateKey($node=NULL) {
1021         if (empty($node)) {
1022             $node = $this->sigNode;
1023         }
1024         if (! $node instanceof DOMNode) {
1025             return NULL;
1026         }
1027         if ($doc = $node->ownerDocument) {
1028             $xpath = new DOMXPath($doc);
1029             $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1030             $query = "string(./secdsig:SignedInfo/secdsig:SignatureMethod/@Algorithm)";
1031             $algorithm = $xpath->evaluate($query, $node);
1032             if ($algorithm) {
1033                 try {
1034                     $objKey = new XMLSecurityKey($algorithm, array('type'=>'public'));
1035                 } catch (Exception $e) {
1036                     return NULL;
1037                 }
1038                 return $objKey;
1039             }
1040         }
1041         return NULL;
1042     }
1043
1044     public function verify($objKey) {
1045         $doc = $this->sigNode->ownerDocument;
1046         $xpath = new DOMXPath($doc);
1047         $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1048         $query = "string(./secdsig:SignatureValue)";
1049         $sigValue = $xpath->evaluate($query, $this->sigNode);
1050         if (empty($sigValue)) {
1051             throw new Exception("Unable to locate SignatureValue");
1052         }
1053         return $objKey->verifySignature($this->signedInfo, base64_decode($sigValue));
1054     }
1055
1056     public function signData($objKey, $data) {
1057         return $objKey->signData($data);
1058     }
1059
1060     public function sign($objKey) {
1061         if ($xpath = $this->getXPathObj()) {
1062             $query = "./secdsig:SignedInfo";
1063             $nodeset = $xpath->query($query, $this->sigNode);
1064             if ($sInfo = $nodeset->item(0)) {
1065                 $query = "./secdsig:SignatureMethod";
1066                 $nodeset = $xpath->query($query, $sInfo);
1067                 $sMethod = $nodeset->item(0);
1068                 $sMethod->setAttribute('Algorithm', $objKey->type);
1069                 $data = $this->canonicalizeData($sInfo, $this->canonicalMethod);
1070                 $sigValue = base64_encode($this->signData($objKey, $data));
1071                 $sigValueNode = $this->createNewSignNode('SignatureValue', $sigValue);
1072                 if ($infoSibling = $sInfo->nextSibling) {
1073                     $infoSibling->parentNode->insertBefore($sigValueNode, $infoSibling);
1074                 } else {
1075                     $this->sigNode->appendChild($sigValueNode);
1076                 }
1077             }
1078         }
1079     }
1080
1081     public function appendCert() {
1082
1083     }
1084
1085     public function appendKey($objKey, $parent=NULL) {
1086         $objKey->serializeKey($parent);
1087     }
1088
1089
1090     /**
1091      * This function inserts the signature element.
1092      *
1093      * The signature element will be appended to the element, unless $beforeNode is specified. If $beforeNode
1094      * is specified, the signature element will be inserted as the last element before $beforeNode.
1095      *
1096      * @param $node  The node the signature element should be inserted into.
1097      * @param $beforeNode  The node the signature element should be located before.
1098      */
1099     public function insertSignature($node, $beforeNode = NULL) {
1100
1101         $document = $node->ownerDocument;
1102         $signatureElement = $document->importNode($this->sigNode, TRUE);
1103
1104         if($beforeNode == NULL) {
1105             $node->insertBefore($signatureElement);
1106         } else {
1107             $node->insertBefore($signatureElement, $beforeNode);
1108         }
1109     }
1110
1111     public function appendSignature($parentNode, $insertBefore = FALSE) {
1112         $beforeNode = $insertBefore ? $parentNode->firstChild : NULL;
1113         $this->insertSignature($parentNode, $beforeNode);
1114     }
1115
1116     static function get509XCert($cert, $isPEMFormat=TRUE) {
1117         $certs = XMLSecurityDSig::staticGet509XCerts($cert, $isPEMFormat);
1118         if (! empty($certs)) {
1119             return $certs[0];
1120         }
1121         return '';
1122     }
1123
1124     static function staticGet509XCerts($certs, $isPEMFormat=TRUE) {
1125         if ($isPEMFormat) {
1126             $data = '';
1127             $certlist = array();
1128             $arCert = explode("\n", $certs);
1129             $inData = FALSE;
1130             foreach ($arCert AS $curData) {
1131                 if (! $inData) {
1132                     if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
1133                         $inData = TRUE;
1134                     }
1135                 } else {
1136                     if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
1137                         $inData = FALSE;
1138                         $certlist[] = $data;
1139                         $data = '';
1140                         continue;
1141                     }
1142                     $data .= trim($curData);
1143                 }
1144             }
1145             return $certlist;
1146         } else {
1147             return array($certs);
1148         }
1149     }
1150
1151     static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=TRUE, $isURL=False, $xpath=NULL) {
1152           if ($isURL) {
1153             $cert = file_get_contents($cert);
1154           }
1155           if (! $parentRef instanceof DOMElement) {
1156             throw new Exception('Invalid parent Node parameter');
1157           }
1158           $baseDoc = $parentRef->ownerDocument;
1159
1160           if (empty($xpath)) {
1161               $xpath = new DOMXPath($parentRef->ownerDocument);
1162               $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1163           }
1164
1165          $query = "./secdsig:KeyInfo";
1166          $nodeset = $xpath->query($query, $parentRef);
1167          $keyInfo = $nodeset->item(0);
1168          if (! $keyInfo) {
1169               $inserted = FALSE;
1170               $keyInfo = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:KeyInfo');
1171
1172                $query = "./secdsig:Object";
1173                $nodeset = $xpath->query($query, $parentRef);
1174                if ($sObject = $nodeset->item(0)) {
1175                     $sObject->parentNode->insertBefore($keyInfo, $sObject);
1176                     $inserted = TRUE;
1177                }
1178
1179               if (! $inserted) {
1180                    $parentRef->appendChild($keyInfo);
1181               }
1182          }
1183
1184          // Add all certs if there are more than one
1185          $certs = XMLSecurityDSig::staticGet509XCerts($cert, $isPEMFormat);
1186
1187          // Atach X509 data node
1188          $x509DataNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Data');
1189          $keyInfo->appendChild($x509DataNode);
1190
1191          // Atach all certificate nodes
1192          foreach ($certs as $X509Cert){
1193             $x509CertNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Certificate', $X509Cert);
1194          $x509DataNode->appendChild($x509CertNode);
1195          }
1196      }
1197
1198     public function add509Cert($cert, $isPEMFormat=TRUE, $isURL=False) {
1199          if ($xpath = $this->getXPathObj()) {
1200             self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath);
1201          }
1202     }
1203     
1204     /* This function retrieves an associative array of the validated nodes.
1205      *
1206      * The array will contain the id of the referenced node as the key and the node itself
1207      * as the value.
1208      *
1209      * Returns:
1210      *  An associative array of validated nodes or NULL if no nodes have been validated.
1211      */
1212     public function getValidatedNodes() {
1213         return $this->validatedNodes;
1214     }
1215 }
1216
1217 class XMLSecEnc {
1218     const template = "<xenc:EncryptedData xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'>
1219    <xenc:CipherData>
1220       <xenc:CipherValue></xenc:CipherValue>
1221    </xenc:CipherData>
1222 </xenc:EncryptedData>";
1223
1224     const Element = 'http://www.w3.org/2001/04/xmlenc#Element';
1225     const Content = 'http://www.w3.org/2001/04/xmlenc#Content';
1226     const URI = 3;
1227     const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#';
1228
1229     private $encdoc = NULL;
1230     private $rawNode = NULL;
1231     public $type = NULL;
1232     public $encKey = NULL;
1233
1234     public function __construct() {
1235         $this->encdoc = new DOMDocument();
1236         $this->encdoc->loadXML(XMLSecEnc::template);
1237     }
1238
1239     public function setNode($node) {
1240         $this->rawNode = $node;
1241     }
1242
1243     public function encryptNode($objKey, $replace=TRUE) {
1244         $data = '';
1245         if (empty($this->rawNode)) {
1246             throw new Exception('Node to encrypt has not been set');
1247         }
1248         if (! $objKey instanceof XMLSecurityKey) {
1249             throw new Exception('Invalid Key');
1250         }
1251         $doc = $this->rawNode->ownerDocument;
1252         $xPath = new DOMXPath($this->encdoc);
1253         $objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue');
1254         $cipherValue = $objList->item(0);
1255         if ($cipherValue == NULL) {
1256             throw new Exception('Error locating CipherValue element within template');
1257         }
1258         switch ($this->type) {
1259             case (XMLSecEnc::Element):
1260                 $data = $doc->saveXML($this->rawNode);
1261                 $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Element);
1262                 break;
1263             case (XMLSecEnc::Content):
1264                 $children = $this->rawNode->childNodes;
1265                 foreach ($children AS $child) {
1266                     $data .= $doc->saveXML($child);
1267                 }
1268                 $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Content);
1269                 break;
1270             default:
1271                 throw new Exception('Type is currently not supported');
1272                 return;
1273         }
1274
1275         $encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptionMethod'));
1276         $encMethod->setAttribute('Algorithm', $objKey->getAlgorith());
1277         $cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode);
1278
1279         $strEncrypt = base64_encode($objKey->encryptData($data));
1280         $value = $this->encdoc->createTextNode($strEncrypt);
1281         $cipherValue->appendChild($value);
1282
1283         if ($replace) {
1284             switch ($this->type) {
1285                 case (XMLSecEnc::Element):
1286                     if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1287                         return $this->encdoc;
1288                     }
1289                     $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, TRUE);
1290                     $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
1291                     return $importEnc;
1292                     break;
1293                 case (XMLSecEnc::Content):
1294                     $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, TRUE);
1295                     while($this->rawNode->firstChild) {
1296                         $this->rawNode->removeChild($this->rawNode->firstChild);
1297                     }
1298                     $this->rawNode->appendChild($importEnc);
1299                     return $importEnc;
1300                     break;
1301             }
1302         }
1303     }
1304
1305     public function decryptNode($objKey, $replace=TRUE) {
1306         $data = '';
1307         if (empty($this->rawNode)) {
1308             throw new Exception('Node to decrypt has not been set');
1309         }
1310         if (! $objKey instanceof XMLSecurityKey) {
1311             throw new Exception('Invalid Key');
1312         }
1313         $doc = $this->rawNode->ownerDocument;
1314         $xPath = new DOMXPath($doc);
1315         $xPath->registerNamespace('xmlencr', XMLSecEnc::XMLENCNS);
1316         /* Only handles embedded content right now and not a reference */
1317         $query = "./xmlencr:CipherData/xmlencr:CipherValue";
1318         $nodeset = $xPath->query($query, $this->rawNode);
1319
1320         if ($node = $nodeset->item(0)) {
1321             $encryptedData = base64_decode($node->nodeValue);
1322             $decrypted = $objKey->decryptData($encryptedData);
1323             if ($replace) {
1324                 switch ($this->type) {
1325                     case (XMLSecEnc::Element):
1326                         $newdoc = new DOMDocument();
1327                         $newdoc->loadXML($decrypted);
1328                         if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1329                             return $newdoc;
1330                         }
1331                         $importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, TRUE);
1332                         $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
1333                         return $importEnc;
1334                         break;
1335                     case (XMLSecEnc::Content):
1336                         if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1337                             $doc = $this->rawNode;
1338                         } else {
1339                             $doc = $this->rawNode->ownerDocument;
1340                         }
1341                         $newFrag = $doc->createDocumentFragment();
1342                         $newFrag->appendXML($decrypted);
1343                         $parent = $this->rawNode->parentNode;
1344                         $parent->replaceChild($newFrag, $this->rawNode);
1345                         return $parent;
1346                         break;
1347                     default:
1348                         return $decrypted;
1349                 }
1350             } else {
1351                 return $decrypted;
1352             }
1353         } else {
1354             throw new Exception("Cannot locate encrypted data");
1355         }
1356     }
1357
1358     public function encryptKey($srcKey, $rawKey, $append=TRUE) {
1359         if ((! $srcKey instanceof XMLSecurityKey) || (! $rawKey instanceof XMLSecurityKey)) {
1360             throw new Exception('Invalid Key');
1361         }
1362         $strEncKey = base64_encode($srcKey->encryptData($rawKey->key));
1363         $root = $this->encdoc->documentElement;
1364         $encKey = $this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptedKey');
1365         if ($append) {
1366             $keyInfo = $root->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'));
1367             $keyInfo->appendChild($encKey);
1368         } else {
1369             $this->encKey = $encKey;
1370         }
1371         $encMethod = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptionMethod'));
1372         $encMethod->setAttribute('Algorithm', $srcKey->getAlgorith());
1373         if (! empty($srcKey->name)) {
1374             $keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'));
1375             $keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name));
1376         }
1377         $cipherData = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherData'));
1378         $cipherData->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherValue', $strEncKey));
1379         return;
1380     }
1381
1382     public function decryptKey($encKey) {
1383         if (! $encKey->isEncrypted) {
1384             throw new Exception("Key is not Encrypted");
1385         }
1386         if (empty($encKey->key)) {
1387             throw new Exception("Key is missing data to perform the decryption");
1388         }
1389         return $this->decryptNode($encKey, FALSE);
1390     }
1391
1392     public function locateEncryptedData($element) {
1393         if ($element instanceof DOMDocument) {
1394             $doc = $element;
1395         } else {
1396             $doc = $element->ownerDocument;
1397         }
1398         if ($doc) {
1399             $xpath = new DOMXPath($doc);
1400             $query = "//*[local-name()='EncryptedData' and namespace-uri()='".XMLSecEnc::XMLENCNS."']";
1401             $nodeset = $xpath->query($query);
1402             return $nodeset->item(0);
1403         }
1404         return NULL;
1405     }
1406
1407     public function locateKey($node=NULL) {
1408         if (empty($node)) {
1409             $node = $this->rawNode;
1410         }
1411         if (! $node instanceof DOMNode) {
1412             return NULL;
1413         }
1414         if ($doc = $node->ownerDocument) {
1415             $xpath = new DOMXPath($doc);
1416             $xpath->registerNamespace('xmlsecenc', XMLSecEnc::XMLENCNS);
1417             $query = ".//xmlsecenc:EncryptionMethod";
1418             $nodeset = $xpath->query($query, $node);
1419             if ($encmeth = $nodeset->item(0)) {
1420                    $attrAlgorithm = $encmeth->getAttribute("Algorithm");
1421                 try {
1422                     $objKey = new XMLSecurityKey($attrAlgorithm, array('type'=>'private'));
1423                 } catch (Exception $e) {
1424                     return NULL;
1425                 }
1426                 return $objKey;
1427             }
1428         }
1429         return NULL;
1430     }
1431
1432     static function staticLocateKeyInfo($objBaseKey=NULL, $node=NULL) {
1433         if (empty($node) || (! $node instanceof DOMNode)) {
1434             return NULL;
1435         }
1436         if ($doc = $node->ownerDocument) {
1437             $xpath = new DOMXPath($doc);
1438             $xpath->registerNamespace('xmlsecenc', XMLSecEnc::XMLENCNS);
1439             $xpath->registerNamespace('xmlsecdsig', XMLSecurityDSig::XMLDSIGNS);
1440             $query = "./xmlsecdsig:KeyInfo";
1441             $nodeset = $xpath->query($query, $node);
1442             if ($encmeth = $nodeset->item(0)) {
1443                 foreach ($encmeth->childNodes AS $child) {
1444                     switch ($child->localName) {
1445                         case 'KeyName':
1446                             if (! empty($objBaseKey)) {
1447                                 $objBaseKey->name = $child->nodeValue;
1448                             }
1449                             break;
1450                         case 'KeyValue':
1451                             foreach ($child->childNodes AS $keyval) {
1452                                 switch ($keyval->localName) {
1453                                     case 'DSAKeyValue':
1454                                         throw new Exception("DSAKeyValue currently not supported");
1455                                         break;
1456                                     case 'RSAKeyValue':
1457                                         $modulus = NULL;
1458                                         $exponent = NULL;
1459                                         if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) {
1460                                             $modulus = base64_decode($modulusNode->nodeValue);
1461                                         }
1462                                         if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) {
1463                                             $exponent = base64_decode($exponentNode->nodeValue);
1464                                         }
1465                                         if (empty($modulus) || empty($exponent)) {
1466                                             throw new Exception("Missing Modulus or Exponent");
1467                                         }
1468                                         $publicKey = XMLSecurityKey::convertRSA($modulus, $exponent);
1469                                         $objBaseKey->loadKey($publicKey);
1470                                         break;
1471                                 }
1472                             }
1473                             break;
1474                         case 'RetrievalMethod':
1475                             /* Not currently supported */
1476                             break;
1477                         case 'EncryptedKey':
1478                             $objenc = new XMLSecEnc();
1479                             $objenc->setNode($child);
1480                             if (! $objKey = $objenc->locateKey()) {
1481                                 throw new Exception("Unable to locate algorithm for this Encrypted Key");
1482                             }
1483                             $objKey->isEncrypted = TRUE;
1484                             $objKey->encryptedCtx = $objenc;
1485                             XMLSecEnc::staticLocateKeyInfo($objKey, $child);
1486                             return $objKey;
1487                             break;
1488                         case 'X509Data':
1489                             if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) {
1490                                 if ($x509certNodes->length > 0) {
1491                                     $x509cert = $x509certNodes->item(0)->textContent;
1492                                     $x509cert = str_replace(array("\r", "\n"), "", $x509cert);
1493                                     $x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n";
1494                                     $objBaseKey->loadKey($x509cert, FALSE, TRUE);
1495                                 }
1496                             }
1497                             break;
1498                     }
1499                 }
1500             }
1501             return $objBaseKey;
1502         }
1503         return NULL;
1504     }
1505
1506     public function locateKeyInfo($objBaseKey=NULL, $node=NULL) {
1507         if (empty($node)) {
1508             $node = $this->rawNode;
1509         }
1510         return XMLSecEnc::staticLocateKeyInfo($objBaseKey, $node);
1511     }
1512 }