]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/Users/authentication/SAMLAuthenticate/lib/xmlseclibs/xmlseclibs.php
Release 6.5.1
[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                 // Disable this part due to openssl bug on some systems
327                 // that think public key is private key. Certificate
328                 // should still serve as key for verification purposes
329                 // By smalyshev 1 May 2012
330                  // $this->key = openssl_get_publickey($this->key);
331             } else {
332                 $this->key = openssl_get_privatekey($this->key, $this->passphrase);
333             }
334         } else if ($this->cryptParams['cipher'] == MCRYPT_RIJNDAEL_128) {
335             /* Check key length */
336             switch ($this->type) {
337                 case (XMLSecurityKey::AES256_CBC):
338                     if (strlen($this->key) < 25) {
339                         throw new Exception('Key must contain at least 25 characters for this cipher');
340                     }
341                     break;
342                 case (XMLSecurityKey::AES192_CBC):
343                     if (strlen($this->key) < 17) {
344                         throw new Exception('Key must contain at least 17 characters for this cipher');
345                     }
346                     break;
347             }
348         }
349     }
350
351     private function encryptMcrypt($data) {
352         $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
353         $this->iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
354         mcrypt_generic_init($td, $this->key, $this->iv);
355         if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
356             $bs = mcrypt_enc_get_block_size($td);
357             for ($datalen0=$datalen=strlen($data); (($datalen%$bs)!=($bs-1)); $datalen++)
358                 $data.=chr(rand(1, 127));
359             $data.=chr($datalen-$datalen0+1);
360         }
361         $encrypted_data = $this->iv.mcrypt_generic($td, $data);
362         mcrypt_generic_deinit($td);
363         mcrypt_module_close($td);
364         return $encrypted_data;
365     }
366
367     private function decryptMcrypt($data) {
368         $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
369         $iv_length = mcrypt_enc_get_iv_size($td);
370
371         $this->iv = substr($data, 0, $iv_length);
372         $data = substr($data, $iv_length);
373
374         mcrypt_generic_init($td, $this->key, $this->iv);
375         $decrypted_data = mdecrypt_generic($td, $data);
376         mcrypt_generic_deinit($td);
377         mcrypt_module_close($td);
378         if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
379             $dataLen = strlen($decrypted_data);
380             $paddingLength = substr($decrypted_data, $dataLen - 1, 1);
381             $decrypted_data = substr($decrypted_data, 0, $dataLen - ord($paddingLength));
382         }
383         return $decrypted_data;
384     }
385
386     private function encryptOpenSSL($data) {
387         if ($this->cryptParams['type'] == 'public') {
388             if (! openssl_public_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
389                 throw new Exception('Failure encrypting Data');
390                 return;
391             }
392         } else {
393             if (! openssl_private_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
394                 throw new Exception('Failure encrypting Data');
395                 return;
396             }
397         }
398         return $encrypted_data;
399     }
400
401     private function decryptOpenSSL($data) {
402         if ($this->cryptParams['type'] == 'public') {
403             if (! openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
404                 throw new Exception('Failure decrypting Data');
405                 return;
406             }
407         } else {
408             if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
409                 throw new Exception('Failure decrypting Data');
410                 return;
411             }
412         }
413         return $decrypted;
414     }
415
416     private function signOpenSSL($data) {
417             $algo = OPENSSL_ALGO_SHA1;
418             if (! empty($this->cryptParams['digest'])) {
419                 $algo = $this->cryptParams['digest'];
420             }
421         if (! openssl_sign ($data, $signature, $this->key, $algo)) {
422             throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo);
423             return;
424         }
425         return $signature;
426     }
427
428     private function verifyOpenSSL($data, $signature) {
429             $algo = OPENSSL_ALGO_SHA1;
430             if (! empty($this->cryptParams['digest'])) {
431                 $algo = $this->cryptParams['digest'];
432             }
433         return openssl_verify ($data, $signature, $this->key, $algo);
434     }
435
436     public function encryptData($data) {
437         switch ($this->cryptParams['library']) {
438             case 'mcrypt':
439                 return $this->encryptMcrypt($data);
440                 break;
441             case 'openssl':
442                 return $this->encryptOpenSSL($data);
443                 break;
444         }
445     }
446
447     public function decryptData($data) {
448         switch ($this->cryptParams['library']) {
449             case 'mcrypt':
450                 return $this->decryptMcrypt($data);
451                 break;
452             case 'openssl':
453                 return $this->decryptOpenSSL($data);
454                 break;
455         }
456     }
457
458     public function signData($data) {
459         switch ($this->cryptParams['library']) {
460             case 'openssl':
461                 return $this->signOpenSSL($data);
462                 break;
463         }
464     }
465
466     public function verifySignature($data, $signature) {
467         switch ($this->cryptParams['library']) {
468             case 'openssl':
469                 return $this->verifyOpenSSL($data, $signature);
470                 break;
471         }
472     }
473
474     public function getAlgorith() {
475         return $this->cryptParams['method'];
476     }
477
478     static function makeAsnSegment($type, $string) {
479         switch ($type){
480             case 0x02:
481                 if (ord($string) > 0x7f)
482                     $string = chr(0).$string;
483                 break;
484             case 0x03:
485                 $string = chr(0).$string;
486                 break;
487         }
488
489         $length = strlen($string);
490
491         if ($length < 128){
492            $output = sprintf("%c%c%s", $type, $length, $string);
493         } else if ($length < 0x0100){
494            $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string);
495         } else if ($length < 0x010000) {
496            $output = sprintf("%c%c%c%c%s", $type, 0x82, $length/0x0100, $length%0x0100, $string);
497         } else {
498             $output = NULL;
499         }
500         return($output);
501     }
502
503     /* Modulus and Exponent must already be base64 decoded */
504     static function convertRSA($modulus, $exponent) {
505         /* make an ASN publicKeyInfo */
506         $exponentEncoding = XMLSecurityKey::makeAsnSegment(0x02, $exponent);
507         $modulusEncoding = XMLSecurityKey::makeAsnSegment(0x02, $modulus);
508         $sequenceEncoding = XMLSecurityKey:: makeAsnSegment(0x30, $modulusEncoding.$exponentEncoding);
509         $bitstringEncoding = XMLSecurityKey::makeAsnSegment(0x03, $sequenceEncoding);
510         $rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500");
511         $publicKeyInfo = XMLSecurityKey::makeAsnSegment (0x30, $rsaAlgorithmIdentifier.$bitstringEncoding);
512
513         /* encode the publicKeyInfo in base64 and add PEM brackets */
514         $publicKeyInfoBase64 = base64_encode($publicKeyInfo);
515         $encoding = "-----BEGIN PUBLIC KEY-----\n";
516         $offset = 0;
517         while ($segment=substr($publicKeyInfoBase64, $offset, 64)){
518            $encoding = $encoding.$segment."\n";
519            $offset += 64;
520         }
521         return $encoding."-----END PUBLIC KEY-----\n";
522     }
523
524     public function serializeKey($parent) {
525
526     }
527
528
529
530     /**
531      * Retrieve the X509 certificate this key represents.
532      *
533      * Will return the X509 certificate in PEM-format if this key represents
534      * an X509 certificate.
535      *
536      * @return  The X509 certificate or NULL if this key doesn't represent an X509-certificate.
537      */
538     public function getX509Certificate() {
539         return $this->x509Certificate;
540     }
541
542 }
543
544 class XMLSecurityDSig {
545     const XMLDSIGNS = 'http://www.w3.org/2000/09/xmldsig#';
546     const SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1';
547     const SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256';
548     const SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512';
549     const RIPEMD160 = 'http://www.w3.org/2001/04/xmlenc#ripemd160';
550
551     const C14N = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
552     const C14N_COMMENTS = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments';
553     const EXC_C14N = 'http://www.w3.org/2001/10/xml-exc-c14n#';
554     const EXC_C14N_COMMENTS = 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments';
555
556     const template = '<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
557   <ds:SignedInfo>
558     <ds:SignatureMethod />
559   </ds:SignedInfo>
560 </ds:Signature>';
561
562     public $sigNode = NULL;
563     public $idKeys = array();
564     public $idNS = array();
565     private $signedInfo = NULL;
566     private $xPathCtx = NULL;
567     private $canonicalMethod = NULL;
568     private $prefix = 'ds';
569     private $searchpfx = 'secdsig';
570
571     /* This variable contains an associative array of validated nodes. */
572     private $validatedNodes = NULL;
573
574     public function __construct() {
575         $sigdoc = new DOMDocument();
576         $sigdoc->loadXML(XMLSecurityDSig::template);
577         $this->sigNode = $sigdoc->documentElement;
578     }
579
580     private function getXPathObj() {
581         if (empty($this->xPathCtx) && ! empty($this->sigNode)) {
582             $xpath = new DOMXPath($this->sigNode->ownerDocument);
583             $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
584             $this->xPathCtx = $xpath;
585         }
586         return $this->xPathCtx;
587     }
588
589     static function generate_GUID($prefix='pfx') {
590         $uuid = md5(uniqid(rand(), true));
591         $guid =  $prefix.substr($uuid,0,8)."-".
592                 substr($uuid,8,4)."-".
593                 substr($uuid,12,4)."-".
594                 substr($uuid,16,4)."-".
595                 substr($uuid,20,12);
596         return $guid;
597     }
598
599     public function locateSignature($objDoc) {
600         if ($objDoc instanceof DOMDocument) {
601             $doc = $objDoc;
602         } else {
603             $doc = $objDoc->ownerDocument;
604         }
605         if ($doc) {
606             $xpath = new DOMXPath($doc);
607             $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
608             $query = ".//secdsig:Signature";
609             $nodeset = $xpath->query($query, $objDoc);
610             $this->sigNode = $nodeset->item(0);
611             return $this->sigNode;
612         }
613         return NULL;
614     }
615
616     public function createNewSignNode($name, $value=NULL) {
617         $doc = $this->sigNode->ownerDocument;
618         if (! is_null($value)) {
619             $node = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, $this->prefix.':'.$name, $value);
620         } else {
621             $node = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, $this->prefix.':'.$name);
622         }
623         return $node;
624     }
625
626     public function setCanonicalMethod($method) {
627         switch ($method) {
628             case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
629             case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
630             case 'http://www.w3.org/2001/10/xml-exc-c14n#':
631             case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
632                 $this->canonicalMethod = $method;
633                 break;
634             default:
635                 throw new Exception('Invalid Canonical Method');
636         }
637         if ($xpath = $this->getXPathObj()) {
638             $query = './'.$this->searchpfx.':SignedInfo';
639             $nodeset = $xpath->query($query, $this->sigNode);
640             if ($sinfo = $nodeset->item(0)) {
641                 $query = './'.$this->searchpfx.'CanonicalizationMethod';
642                 $nodeset = $xpath->query($query, $sinfo);
643                 if (! ($canonNode = $nodeset->item(0))) {
644                     $canonNode = $this->createNewSignNode('CanonicalizationMethod');
645                     $sinfo->insertBefore($canonNode, $sinfo->firstChild);
646                 }
647                 $canonNode->setAttribute('Algorithm', $this->canonicalMethod);
648             }
649         }
650     }
651
652     private function canonicalizeData($node, $canonicalmethod, $arXPath=NULL, $prefixList=NULL) {
653         $exclusive = FALSE;
654         $withComments = FALSE;
655         switch ($canonicalmethod) {
656             case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
657                 $exclusive = FALSE;
658                 $withComments = FALSE;
659                 break;
660             case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
661                 $withComments = TRUE;
662                 break;
663             case 'http://www.w3.org/2001/10/xml-exc-c14n#':
664                 $exclusive = TRUE;
665                 break;
666             case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
667                 $exclusive = TRUE;
668                 $withComments = TRUE;
669                 break;
670         }
671 /* Support PHP versions < 5.2 not containing C14N methods in DOM extension */
672         $php_version = explode('.', PHP_VERSION);
673         if (($php_version[0] < 5) || ($php_version[0] == 5 && $php_version[1] < 2) ) {
674             if (! is_null($arXPath)) {
675                 throw new Exception("PHP 5.2.0 or higher is required to perform XPath Transformations");
676             }
677             return C14NGeneral($node, $exclusive, $withComments);
678         }
679         return $node->C14N($exclusive, $withComments, $arXPath, $prefixList);
680     }
681
682     public function canonicalizeSignedInfo() {
683
684         $doc = $this->sigNode->ownerDocument;
685         $canonicalmethod = NULL;
686         if ($doc) {
687             $xpath = $this->getXPathObj();
688             $query = "./secdsig:SignedInfo";
689             $nodeset = $xpath->query($query, $this->sigNode);
690             if ($signInfoNode = $nodeset->item(0)) {
691                 $query = "./secdsig:CanonicalizationMethod";
692                 $nodeset = $xpath->query($query, $signInfoNode);
693                 if ($canonNode = $nodeset->item(0)) {
694                     $canonicalmethod = $canonNode->getAttribute('Algorithm');
695                 }
696                 $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod);
697                 return $this->signedInfo;
698             }
699         }
700         return NULL;
701     }
702
703     public function calculateDigest ($digestAlgorithm, $data) {
704         switch ($digestAlgorithm) {
705             case XMLSecurityDSig::SHA1:
706                 $alg = 'sha1';
707                 break;
708             case XMLSecurityDSig::SHA256:
709                 $alg = 'sha256';
710                 break;
711             case XMLSecurityDSig::SHA512:
712                 $alg = 'sha512';
713                 break;
714             case XMLSecurityDSig::RIPEMD160:
715                 $alg = 'ripemd160';
716                 break;
717             default:
718                 throw new Exception("Cannot validate digest: Unsupported Algorith <$digestAlgorithm>");
719         }
720         if (function_exists('hash')) {
721             return base64_encode(hash($alg, $data, TRUE));
722         } elseif (function_exists('mhash')) {
723             $alg = "MHASH_" . strtoupper($alg);
724             return base64_encode(mhash(constant($alg), $data));
725         } elseif ($alg === 'sha1') {
726             return base64_encode(sha1($data, TRUE));
727         } else {
728             throw new Exception('xmlseclibs is unable to calculate a digest. Maybe you need the mhash library?');
729         }
730     }
731
732     public function validateDigest($refNode, $data) {
733         $xpath = new DOMXPath($refNode->ownerDocument);
734         $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
735         $query = 'string(./secdsig:DigestMethod/@Algorithm)';
736         $digestAlgorithm = $xpath->evaluate($query, $refNode);
737         $digValue = $this->calculateDigest($digestAlgorithm, $data);
738         $query = 'string(./secdsig:DigestValue)';
739         $digestValue = $xpath->evaluate($query, $refNode);
740         return ($digValue == $digestValue);
741     }
742
743     public function processTransforms($refNode, $objData) {
744         $data = $objData;
745         $xpath = new DOMXPath($refNode->ownerDocument);
746         $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
747         $query = './secdsig:Transforms/secdsig:Transform';
748         $nodelist = $xpath->query($query, $refNode);
749         $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
750         $arXPath = NULL;
751         $prefixList = NULL;
752         foreach ($nodelist AS $transform) {
753             $algorithm = $transform->getAttribute("Algorithm");
754             switch ($algorithm) {
755                 case 'http://www.w3.org/2001/10/xml-exc-c14n#':
756                 case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
757                     $node = $transform->firstChild;
758                     while ($node) {
759                         if ($node->localName == 'InclusiveNamespaces') {
760                             if ($pfx = $node->getAttribute('PrefixList')) {
761                                 $arpfx = array();
762                                 $pfxlist = preg_split("/\s/", $pfx);
763                                 foreach ($pfxlist AS $pfx) {
764                                     $val = trim($pfx);
765                                     if (! empty($val)) {
766                                         $arpfx[] = $val;
767                                     }
768                                 }
769                                 if (count($arpfx) > 0) {
770                                     $prefixList = $arpfx;
771                                 }
772                             }
773                             break;
774                         }
775                         $node = $node->nextSibling;
776                     }
777                 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
778                 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
779                     $canonicalMethod = $algorithm;
780                     break;
781                 case 'http://www.w3.org/TR/1999/REC-xpath-19991116':
782                     $node = $transform->firstChild;
783                     while ($node) {
784                         if ($node->localName == 'XPath') {
785                             $arXPath = array();
786                             $arXPath['query'] = '(.//. | .//@* | .//namespace::*)['.$node->nodeValue.']';
787                             $arXpath['namespaces'] = array();
788                             $nslist = $xpath->query('./namespace::*', $node);
789                             foreach ($nslist AS $nsnode) {
790                                 if ($nsnode->localName != "xml") {
791                                     $arXPath['namespaces'][$nsnode->localName] = $nsnode->nodeValue;
792                                 }
793                             }
794                             break;
795                         }
796                         $node = $node->nextSibling;
797                     }
798                     break;
799             }
800         }
801         if ($data instanceof DOMNode) {
802             $data = $this->canonicalizeData($objData, $canonicalMethod, $arXPath, $prefixList);
803         }
804         return $data;
805     }
806
807     public function processRefNode($refNode) {
808         $dataObject = NULL;
809         if ($uri = $refNode->getAttribute("URI")) {
810             $arUrl = parse_url($uri);
811             if (empty($arUrl['path'])) {
812                 if ($identifier = $arUrl['fragment']) {
813                     $xPath = new DOMXPath($refNode->ownerDocument);
814                     if ($this->idNS && is_array($this->idNS)) {
815                         foreach ($this->idNS AS $nspf=>$ns) {
816                             $xPath->registerNamespace($nspf, $ns);
817                         }
818                     }
819                     $iDlist = '@Id="'.$identifier.'"';
820                     if (is_array($this->idKeys)) {
821                         foreach ($this->idKeys AS $idKey) {
822                             $iDlist .= " or @$idKey='$identifier'";
823                         }
824                     }
825                     $query = '//*['.$iDlist.']';
826                     $dataObject = $xPath->query($query)->item(0);
827                 } else {
828                     $dataObject = $refNode->ownerDocument;
829                 }
830             } else {
831                 $dataObject = file_get_contents($arUrl);
832             }
833         } else {
834             $dataObject = $refNode->ownerDocument;
835         }
836         $data = $this->processTransforms($refNode, $dataObject);
837         if (!$this->validateDigest($refNode, $data)) {
838             return FALSE;
839         }
840
841         if ($dataObject instanceof DOMNode) {
842             /* Add this node to the list of validated nodes. */
843             if(! empty($identifier)) {
844                 $this->validatedNodes[$identifier] = $dataObject;
845             } else {
846                 $this->validatedNodes[] = $dataObject;
847             }
848         }
849
850         return TRUE;
851     }
852
853     public function getRefNodeID($refNode) {
854         if ($uri = $refNode->getAttribute("URI")) {
855             $arUrl = parse_url($uri);
856             if (empty($arUrl['path'])) {
857                 if ($identifier = $arUrl['fragment']) {
858                     return $identifier;
859                 }
860             }
861         }
862         return null;
863     }
864
865     public function getRefIDs() {
866         $refids = array();
867         $doc = $this->sigNode->ownerDocument;
868
869         $xpath = $this->getXPathObj();
870         $query = "./secdsig:SignedInfo/secdsig:Reference";
871         $nodeset = $xpath->query($query, $this->sigNode);
872         if ($nodeset->length == 0) {
873             throw new Exception("Reference nodes not found");
874         }
875         foreach ($nodeset AS $refNode) {
876             $refids[] = $this->getRefNodeID($refNode);
877         }
878         return $refids;
879     }
880
881     public function validateReference() {
882         $doc = $this->sigNode->ownerDocument;
883         if (! $doc->isSameNode($this->sigNode)) {
884             $this->sigNode->parentNode->removeChild($this->sigNode);
885         }
886         $xpath = $this->getXPathObj();
887         $query = "./secdsig:SignedInfo/secdsig:Reference";
888         $nodeset = $xpath->query($query, $this->sigNode);
889         if ($nodeset->length == 0) {
890             throw new Exception("Reference nodes not found");
891         }
892
893         /* Initialize/reset the list of validated nodes. */
894         $this->validatedNodes = array();
895
896         foreach ($nodeset AS $refNode) {
897             if (! $this->processRefNode($refNode)) {
898                 /* Clear the list of validated nodes. */
899                 $this->validatedNodes = NULL;
900                 throw new Exception("Reference validation failed");
901             }
902         }
903         return TRUE;
904     }
905
906     private function addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=NULL, $options=NULL) {
907         $prefix = NULL;
908         $prefix_ns = NULL;
909         $id_name = 'Id';
910         $overwrite_id  = TRUE;
911         $force_uri = FALSE;
912
913         if (is_array($options)) {
914             $prefix = empty($options['prefix'])?NULL:$options['prefix'];
915             $prefix_ns = empty($options['prefix_ns'])?NULL:$options['prefix_ns'];
916             $id_name = empty($options['id_name'])?'Id':$options['id_name'];
917             $overwrite_id = !isset($options['overwrite'])?TRUE:(bool)$options['overwrite'];
918             $force_uri = !isset($options['force_uri'])?FALSE:(bool)$options['force_uri'];
919         }
920
921         $attname = $id_name;
922         if (! empty($prefix)) {
923             $attname = $prefix.':'.$attname;
924         }
925
926         $refNode = $this->createNewSignNode('Reference');
927         $sinfoNode->appendChild($refNode);
928
929         if (! $node instanceof DOMDocument) {
930             $uri = NULL;
931             if (! $overwrite_id) {
932                 $uri = $node->getAttributeNS($prefix_ns, $attname);
933             }
934             if (empty($uri)) {
935                 $uri = XMLSecurityDSig::generate_GUID();
936                 $node->setAttributeNS($prefix_ns, $attname, $uri);
937             }
938             $refNode->setAttribute("URI", '#'.$uri);
939         } elseif ($force_uri) {
940             $refNode->setAttribute("URI", '');
941         }
942
943         $transNodes = $this->createNewSignNode('Transforms');
944         $refNode->appendChild($transNodes);
945
946         if (is_array($arTransforms)) {
947             foreach ($arTransforms AS $transform) {
948                 $transNode = $this->createNewSignNode('Transform');
949                 $transNodes->appendChild($transNode);
950                 if (is_array($transform) &&
951                     (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116'])) &&
952                     (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']))) {
953                     $transNode->setAttribute('Algorithm', 'http://www.w3.org/TR/1999/REC-xpath-19991116');
954                     $XPathNode = $this->createNewSignNode('XPath', $transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']);
955                     $transNode->appendChild($XPathNode);
956                     if (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'])) {
957                         foreach ($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'] AS $prefix => $namespace) {
958                             $XPathNode->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:$prefix", $namespace);
959                         }
960                     }
961                 } else {
962                     $transNode->setAttribute('Algorithm', $transform);
963                 }
964             }
965         } elseif (! empty($this->canonicalMethod)) {
966             $transNode = $this->createNewSignNode('Transform');
967             $transNodes->appendChild($transNode);
968             $transNode->setAttribute('Algorithm', $this->canonicalMethod);
969         }
970
971         $canonicalData = $this->processTransforms($refNode, $node);
972         $digValue = $this->calculateDigest($algorithm, $canonicalData);
973
974         $digestMethod = $this->createNewSignNode('DigestMethod');
975         $refNode->appendChild($digestMethod);
976         $digestMethod->setAttribute('Algorithm', $algorithm);
977
978         $digestValue = $this->createNewSignNode('DigestValue', $digValue);
979         $refNode->appendChild($digestValue);
980     }
981
982     public function addReference($node, $algorithm, $arTransforms=NULL, $options=NULL) {
983         if ($xpath = $this->getXPathObj()) {
984             $query = "./secdsig:SignedInfo";
985             $nodeset = $xpath->query($query, $this->sigNode);
986             if ($sInfo = $nodeset->item(0)) {
987                 $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options);
988             }
989         }
990     }
991
992     public function addReferenceList($arNodes, $algorithm, $arTransforms=NULL, $options=NULL) {
993         if ($xpath = $this->getXPathObj()) {
994             $query = "./secdsig:SignedInfo";
995             $nodeset = $xpath->query($query, $this->sigNode);
996             if ($sInfo = $nodeset->item(0)) {
997                 foreach ($arNodes AS $node) {
998                     $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options);
999                 }
1000             }
1001         }
1002     }
1003
1004    public function addObject($data, $mimetype=NULL, $encoding=NULL) {
1005       $objNode = $this->createNewSignNode('Object');
1006       $this->sigNode->appendChild($objNode);
1007       if (! empty($mimetype)) {
1008          $objNode->setAtribute('MimeType', $mimetype);
1009       }
1010       if (! empty($encoding)) {
1011          $objNode->setAttribute('Encoding', $encoding);
1012       }
1013
1014       if ($data instanceof DOMElement) {
1015          $newData = $this->sigNode->ownerDocument->importNode($data, TRUE);
1016       } else {
1017          $newData = $this->sigNode->ownerDocument->createTextNode($data);
1018       }
1019       $objNode->appendChild($newData);
1020
1021       return $objNode;
1022    }
1023
1024     public function locateKey($node=NULL) {
1025         if (empty($node)) {
1026             $node = $this->sigNode;
1027         }
1028         if (! $node instanceof DOMNode) {
1029             return NULL;
1030         }
1031         if ($doc = $node->ownerDocument) {
1032             $xpath = new DOMXPath($doc);
1033             $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1034             $query = "string(./secdsig:SignedInfo/secdsig:SignatureMethod/@Algorithm)";
1035             $algorithm = $xpath->evaluate($query, $node);
1036             if ($algorithm) {
1037                 try {
1038                     $objKey = new XMLSecurityKey($algorithm, array('type'=>'public'));
1039                 } catch (Exception $e) {
1040                     return NULL;
1041                 }
1042                 return $objKey;
1043             }
1044         }
1045         return NULL;
1046     }
1047
1048     public function verify($objKey) {
1049         $doc = $this->sigNode->ownerDocument;
1050         $xpath = new DOMXPath($doc);
1051         $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1052         $query = "string(./secdsig:SignatureValue)";
1053         $sigValue = $xpath->evaluate($query, $this->sigNode);
1054         if (empty($sigValue)) {
1055             throw new Exception("Unable to locate SignatureValue");
1056         }
1057         return $objKey->verifySignature($this->signedInfo, base64_decode($sigValue));
1058     }
1059
1060     public function signData($objKey, $data) {
1061         return $objKey->signData($data);
1062     }
1063
1064     public function sign($objKey) {
1065         if ($xpath = $this->getXPathObj()) {
1066             $query = "./secdsig:SignedInfo";
1067             $nodeset = $xpath->query($query, $this->sigNode);
1068             if ($sInfo = $nodeset->item(0)) {
1069                 $query = "./secdsig:SignatureMethod";
1070                 $nodeset = $xpath->query($query, $sInfo);
1071                 $sMethod = $nodeset->item(0);
1072                 $sMethod->setAttribute('Algorithm', $objKey->type);
1073                 $data = $this->canonicalizeData($sInfo, $this->canonicalMethod);
1074                 $sigValue = base64_encode($this->signData($objKey, $data));
1075                 $sigValueNode = $this->createNewSignNode('SignatureValue', $sigValue);
1076                 if ($infoSibling = $sInfo->nextSibling) {
1077                     $infoSibling->parentNode->insertBefore($sigValueNode, $infoSibling);
1078                 } else {
1079                     $this->sigNode->appendChild($sigValueNode);
1080                 }
1081             }
1082         }
1083     }
1084
1085     public function appendCert() {
1086
1087     }
1088
1089     public function appendKey($objKey, $parent=NULL) {
1090         $objKey->serializeKey($parent);
1091     }
1092
1093
1094     /**
1095      * This function inserts the signature element.
1096      *
1097      * The signature element will be appended to the element, unless $beforeNode is specified. If $beforeNode
1098      * is specified, the signature element will be inserted as the last element before $beforeNode.
1099      *
1100      * @param $node  The node the signature element should be inserted into.
1101      * @param $beforeNode  The node the signature element should be located before.
1102      */
1103     public function insertSignature($node, $beforeNode = NULL) {
1104
1105         $document = $node->ownerDocument;
1106         $signatureElement = $document->importNode($this->sigNode, TRUE);
1107
1108         if($beforeNode == NULL) {
1109             $node->insertBefore($signatureElement);
1110         } else {
1111             $node->insertBefore($signatureElement, $beforeNode);
1112         }
1113     }
1114
1115     public function appendSignature($parentNode, $insertBefore = FALSE) {
1116         $beforeNode = $insertBefore ? $parentNode->firstChild : NULL;
1117         $this->insertSignature($parentNode, $beforeNode);
1118     }
1119
1120     static function get509XCert($cert, $isPEMFormat=TRUE) {
1121         $certs = XMLSecurityDSig::staticGet509XCerts($cert, $isPEMFormat);
1122         if (! empty($certs)) {
1123             return $certs[0];
1124         }
1125         return '';
1126     }
1127
1128     static function staticGet509XCerts($certs, $isPEMFormat=TRUE) {
1129         if ($isPEMFormat) {
1130             $data = '';
1131             $certlist = array();
1132             $arCert = explode("\n", $certs);
1133             $inData = FALSE;
1134             foreach ($arCert AS $curData) {
1135                 if (! $inData) {
1136                     if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
1137                         $inData = TRUE;
1138                     }
1139                 } else {
1140                     if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
1141                         $inData = FALSE;
1142                         $certlist[] = $data;
1143                         $data = '';
1144                         continue;
1145                     }
1146                     $data .= trim($curData);
1147                 }
1148             }
1149             return $certlist;
1150         } else {
1151             return array($certs);
1152         }
1153     }
1154
1155     static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=TRUE, $isURL=False, $xpath=NULL) {
1156           if ($isURL) {
1157             $cert = file_get_contents($cert);
1158           }
1159           if (! $parentRef instanceof DOMElement) {
1160             throw new Exception('Invalid parent Node parameter');
1161           }
1162           $baseDoc = $parentRef->ownerDocument;
1163
1164           if (empty($xpath)) {
1165               $xpath = new DOMXPath($parentRef->ownerDocument);
1166               $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1167           }
1168
1169          $query = "./secdsig:KeyInfo";
1170          $nodeset = $xpath->query($query, $parentRef);
1171          $keyInfo = $nodeset->item(0);
1172          if (! $keyInfo) {
1173               $inserted = FALSE;
1174               $keyInfo = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:KeyInfo');
1175
1176                $query = "./secdsig:Object";
1177                $nodeset = $xpath->query($query, $parentRef);
1178                if ($sObject = $nodeset->item(0)) {
1179                     $sObject->parentNode->insertBefore($keyInfo, $sObject);
1180                     $inserted = TRUE;
1181                }
1182
1183               if (! $inserted) {
1184                    $parentRef->appendChild($keyInfo);
1185               }
1186          }
1187
1188          // Add all certs if there are more than one
1189          $certs = XMLSecurityDSig::staticGet509XCerts($cert, $isPEMFormat);
1190
1191          // Atach X509 data node
1192          $x509DataNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Data');
1193          $keyInfo->appendChild($x509DataNode);
1194
1195          // Atach all certificate nodes
1196          foreach ($certs as $X509Cert){
1197             $x509CertNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Certificate', $X509Cert);
1198          $x509DataNode->appendChild($x509CertNode);
1199          }
1200      }
1201
1202     public function add509Cert($cert, $isPEMFormat=TRUE, $isURL=False) {
1203          if ($xpath = $this->getXPathObj()) {
1204             self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath);
1205          }
1206     }
1207
1208     /* This function retrieves an associative array of the validated nodes.
1209      *
1210      * The array will contain the id of the referenced node as the key and the node itself
1211      * as the value.
1212      *
1213      * Returns:
1214      *  An associative array of validated nodes or NULL if no nodes have been validated.
1215      */
1216     public function getValidatedNodes() {
1217         return $this->validatedNodes;
1218     }
1219 }
1220
1221 class XMLSecEnc {
1222     const template = "<xenc:EncryptedData xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'>
1223    <xenc:CipherData>
1224       <xenc:CipherValue></xenc:CipherValue>
1225    </xenc:CipherData>
1226 </xenc:EncryptedData>";
1227
1228     const Element = 'http://www.w3.org/2001/04/xmlenc#Element';
1229     const Content = 'http://www.w3.org/2001/04/xmlenc#Content';
1230     const URI = 3;
1231     const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#';
1232
1233     private $encdoc = NULL;
1234     private $rawNode = NULL;
1235     public $type = NULL;
1236     public $encKey = NULL;
1237
1238     public function __construct() {
1239         $this->encdoc = new DOMDocument();
1240         $this->encdoc->loadXML(XMLSecEnc::template);
1241     }
1242
1243     public function setNode($node) {
1244         $this->rawNode = $node;
1245     }
1246
1247     public function encryptNode($objKey, $replace=TRUE) {
1248         $data = '';
1249         if (empty($this->rawNode)) {
1250             throw new Exception('Node to encrypt has not been set');
1251         }
1252         if (! $objKey instanceof XMLSecurityKey) {
1253             throw new Exception('Invalid Key');
1254         }
1255         $doc = $this->rawNode->ownerDocument;
1256         $xPath = new DOMXPath($this->encdoc);
1257         $objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue');
1258         $cipherValue = $objList->item(0);
1259         if ($cipherValue == NULL) {
1260             throw new Exception('Error locating CipherValue element within template');
1261         }
1262         switch ($this->type) {
1263             case (XMLSecEnc::Element):
1264                 $data = $doc->saveXML($this->rawNode);
1265                 $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Element);
1266                 break;
1267             case (XMLSecEnc::Content):
1268                 $children = $this->rawNode->childNodes;
1269                 foreach ($children AS $child) {
1270                     $data .= $doc->saveXML($child);
1271                 }
1272                 $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Content);
1273                 break;
1274             default:
1275                 throw new Exception('Type is currently not supported');
1276                 return;
1277         }
1278
1279         $encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptionMethod'));
1280         $encMethod->setAttribute('Algorithm', $objKey->getAlgorith());
1281         $cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode);
1282
1283         $strEncrypt = base64_encode($objKey->encryptData($data));
1284         $value = $this->encdoc->createTextNode($strEncrypt);
1285         $cipherValue->appendChild($value);
1286
1287         if ($replace) {
1288             switch ($this->type) {
1289                 case (XMLSecEnc::Element):
1290                     if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1291                         return $this->encdoc;
1292                     }
1293                     $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, TRUE);
1294                     $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
1295                     return $importEnc;
1296                     break;
1297                 case (XMLSecEnc::Content):
1298                     $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, TRUE);
1299                     while($this->rawNode->firstChild) {
1300                         $this->rawNode->removeChild($this->rawNode->firstChild);
1301                     }
1302                     $this->rawNode->appendChild($importEnc);
1303                     return $importEnc;
1304                     break;
1305             }
1306         }
1307     }
1308
1309     public function decryptNode($objKey, $replace=TRUE) {
1310         $data = '';
1311         if (empty($this->rawNode)) {
1312             throw new Exception('Node to decrypt has not been set');
1313         }
1314         if (! $objKey instanceof XMLSecurityKey) {
1315             throw new Exception('Invalid Key');
1316         }
1317         $doc = $this->rawNode->ownerDocument;
1318         $xPath = new DOMXPath($doc);
1319         $xPath->registerNamespace('xmlencr', XMLSecEnc::XMLENCNS);
1320         /* Only handles embedded content right now and not a reference */
1321         $query = "./xmlencr:CipherData/xmlencr:CipherValue";
1322         $nodeset = $xPath->query($query, $this->rawNode);
1323
1324         if ($node = $nodeset->item(0)) {
1325             $encryptedData = base64_decode($node->nodeValue);
1326             $decrypted = $objKey->decryptData($encryptedData);
1327             if ($replace) {
1328                 switch ($this->type) {
1329                     case (XMLSecEnc::Element):
1330                         $newdoc = new DOMDocument();
1331                         $newdoc->loadXML($decrypted);
1332                         if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1333                             return $newdoc;
1334                         }
1335                         $importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, TRUE);
1336                         $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
1337                         return $importEnc;
1338                         break;
1339                     case (XMLSecEnc::Content):
1340                         if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1341                             $doc = $this->rawNode;
1342                         } else {
1343                             $doc = $this->rawNode->ownerDocument;
1344                         }
1345                         $newFrag = $doc->createDocumentFragment();
1346                         $newFrag->appendXML($decrypted);
1347                         $parent = $this->rawNode->parentNode;
1348                         $parent->replaceChild($newFrag, $this->rawNode);
1349                         return $parent;
1350                         break;
1351                     default:
1352                         return $decrypted;
1353                 }
1354             } else {
1355                 return $decrypted;
1356             }
1357         } else {
1358             throw new Exception("Cannot locate encrypted data");
1359         }
1360     }
1361
1362     public function encryptKey($srcKey, $rawKey, $append=TRUE) {
1363         if ((! $srcKey instanceof XMLSecurityKey) || (! $rawKey instanceof XMLSecurityKey)) {
1364             throw new Exception('Invalid Key');
1365         }
1366         $strEncKey = base64_encode($srcKey->encryptData($rawKey->key));
1367         $root = $this->encdoc->documentElement;
1368         $encKey = $this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptedKey');
1369         if ($append) {
1370             $keyInfo = $root->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'));
1371             $keyInfo->appendChild($encKey);
1372         } else {
1373             $this->encKey = $encKey;
1374         }
1375         $encMethod = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptionMethod'));
1376         $encMethod->setAttribute('Algorithm', $srcKey->getAlgorith());
1377         if (! empty($srcKey->name)) {
1378             $keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'));
1379             $keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name));
1380         }
1381         $cipherData = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherData'));
1382         $cipherData->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherValue', $strEncKey));
1383         return;
1384     }
1385
1386     public function decryptKey($encKey) {
1387         if (! $encKey->isEncrypted) {
1388             throw new Exception("Key is not Encrypted");
1389         }
1390         if (empty($encKey->key)) {
1391             throw new Exception("Key is missing data to perform the decryption");
1392         }
1393         return $this->decryptNode($encKey, FALSE);
1394     }
1395
1396     public function locateEncryptedData($element) {
1397         if ($element instanceof DOMDocument) {
1398             $doc = $element;
1399         } else {
1400             $doc = $element->ownerDocument;
1401         }
1402         if ($doc) {
1403             $xpath = new DOMXPath($doc);
1404             $query = "//*[local-name()='EncryptedData' and namespace-uri()='".XMLSecEnc::XMLENCNS."']";
1405             $nodeset = $xpath->query($query);
1406             return $nodeset->item(0);
1407         }
1408         return NULL;
1409     }
1410
1411     public function locateKey($node=NULL) {
1412         if (empty($node)) {
1413             $node = $this->rawNode;
1414         }
1415         if (! $node instanceof DOMNode) {
1416             return NULL;
1417         }
1418         if ($doc = $node->ownerDocument) {
1419             $xpath = new DOMXPath($doc);
1420             $xpath->registerNamespace('xmlsecenc', XMLSecEnc::XMLENCNS);
1421             $query = ".//xmlsecenc:EncryptionMethod";
1422             $nodeset = $xpath->query($query, $node);
1423             if ($encmeth = $nodeset->item(0)) {
1424                    $attrAlgorithm = $encmeth->getAttribute("Algorithm");
1425                 try {
1426                     $objKey = new XMLSecurityKey($attrAlgorithm, array('type'=>'private'));
1427                 } catch (Exception $e) {
1428                     return NULL;
1429                 }
1430                 return $objKey;
1431             }
1432         }
1433         return NULL;
1434     }
1435
1436     static function staticLocateKeyInfo($objBaseKey=NULL, $node=NULL) {
1437         if (empty($node) || (! $node instanceof DOMNode)) {
1438             return NULL;
1439         }
1440         if ($doc = $node->ownerDocument) {
1441             $xpath = new DOMXPath($doc);
1442             $xpath->registerNamespace('xmlsecenc', XMLSecEnc::XMLENCNS);
1443             $xpath->registerNamespace('xmlsecdsig', XMLSecurityDSig::XMLDSIGNS);
1444             $query = "./xmlsecdsig:KeyInfo";
1445             $nodeset = $xpath->query($query, $node);
1446             if ($encmeth = $nodeset->item(0)) {
1447                 foreach ($encmeth->childNodes AS $child) {
1448                     switch ($child->localName) {
1449                         case 'KeyName':
1450                             if (! empty($objBaseKey)) {
1451                                 $objBaseKey->name = $child->nodeValue;
1452                             }
1453                             break;
1454                         case 'KeyValue':
1455                             foreach ($child->childNodes AS $keyval) {
1456                                 switch ($keyval->localName) {
1457                                     case 'DSAKeyValue':
1458                                         throw new Exception("DSAKeyValue currently not supported");
1459                                         break;
1460                                     case 'RSAKeyValue':
1461                                         $modulus = NULL;
1462                                         $exponent = NULL;
1463                                         if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) {
1464                                             $modulus = base64_decode($modulusNode->nodeValue);
1465                                         }
1466                                         if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) {
1467                                             $exponent = base64_decode($exponentNode->nodeValue);
1468                                         }
1469                                         if (empty($modulus) || empty($exponent)) {
1470                                             throw new Exception("Missing Modulus or Exponent");
1471                                         }
1472                                         $publicKey = XMLSecurityKey::convertRSA($modulus, $exponent);
1473                                         $objBaseKey->loadKey($publicKey);
1474                                         break;
1475                                 }
1476                             }
1477                             break;
1478                         case 'RetrievalMethod':
1479                             /* Not currently supported */
1480                             break;
1481                         case 'EncryptedKey':
1482                             $objenc = new XMLSecEnc();
1483                             $objenc->setNode($child);
1484                             if (! $objKey = $objenc->locateKey()) {
1485                                 throw new Exception("Unable to locate algorithm for this Encrypted Key");
1486                             }
1487                             $objKey->isEncrypted = TRUE;
1488                             $objKey->encryptedCtx = $objenc;
1489                             XMLSecEnc::staticLocateKeyInfo($objKey, $child);
1490                             return $objKey;
1491                             break;
1492                         case 'X509Data':
1493                             if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) {
1494                                 if ($x509certNodes->length > 0) {
1495                                     $x509cert = $x509certNodes->item(0)->textContent;
1496                                     $x509cert = str_replace(array("\r", "\n"), "", $x509cert);
1497                                     $x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n";
1498                                     $objBaseKey->loadKey($x509cert, FALSE, TRUE);
1499                                 }
1500                             }
1501                             break;
1502                     }
1503                 }
1504             }
1505             return $objBaseKey;
1506         }
1507         return NULL;
1508     }
1509
1510     public function locateKeyInfo($objBaseKey=NULL, $node=NULL) {
1511         if (empty($node)) {
1512             $node = $this->rawNode;
1513         }
1514         return XMLSecEnc::staticLocateKeyInfo($objBaseKey, $node);
1515     }
1516 }