5 * Copyright (c) 2007, Robert Richards <rrichards@cdatazone.org>.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
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
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.
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.
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
44 Functions to generate simple cases of Exclusive Canonical XML - Callable function is C14NGeneral()
45 i.e.: $canonical = C14NGeneral($domelement, TRUE);
49 function sortAndAddAttrs($element, $arAtts) {
51 foreach ($arAtts AS $attnode) {
52 $newAtts[$attnode->nodeName] = $attnode;
55 foreach ($newAtts as $attnode) {
56 $element->setAttribute($attnode->nodeName, $attnode->nodeValue);
61 function canonical($tree, $element, $withcomments) {
62 if ($tree->nodeType != XML_DOCUMENT_NODE) {
63 $dom = $tree->ownerDocument;
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);
74 if ($element->nodeType == XML_COMMENT_NODE && ! $withcomments) {
77 $tree->appendChild($dom->importNode($element, TRUE));
81 if ($element->namespaceURI != "") {
82 if ($element->prefix == "") {
83 $elCopy = $dom->createElementNS($element->namespaceURI, $element->nodeName);
85 $prefix = $tree->lookupPrefix($element->namespaceURI);
86 if ($prefix == $element->prefix) {
87 $elCopy = $dom->createElementNS($element->namespaceURI, $element->nodeName);
89 $elCopy = $dom->createElement($element->nodeName);
90 $arNS[$element->namespaceURI] = $element->prefix;
94 $elCopy = $dom->createElement($element->nodeName);
96 $tree->appendChild($elCopy);
98 /* Create DOMXPath based on original document */
99 $xPath = new DOMXPath($element->ownerDocument);
101 /* Get namespaced attributes */
102 $arAtts = $xPath->query('attribute::*[namespace-uri(.) != ""]', $element);
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)) {
110 $prefix = $tree->lookupPrefix($attnode->namespaceURI);
111 if ($prefix != $attnode->prefix) {
112 $arNS[$attnode->namespaceURI] = $attnode->prefix;
114 $arNS[$attnode->namespaceURI] = NULL;
117 if (count($arNS) > 0) {
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);
128 if (count($arNS) > 0) {
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);
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);
142 foreach ($element->childNodes AS $node) {
143 canonical($elCopy, $node, $withcomments);
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
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);
159 /* Must be element or document */
160 if (! $element instanceof DOMElement && ! $element instanceof DOMDocument) {
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");
168 $copyDoc = new DOMDocument();
169 canonical($copyDoc, $element, $withcomments);
170 return $copyDoc->saveXML($copyDoc->documentElement, LIBXML_NOEMPTYTAG);
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';
184 private $cryptParams = array();
187 public $passphrase = "";
190 public $keyChain = NULL;
191 public $isEncrypted = FALSE;
192 public $encryptedCtx = NULL;
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.
199 private $x509Certificate = NULL;
201 public function __construct($type, $params=NULL) {
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';
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';
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';
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';
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'];
238 throw new Exception('Certificate "type" (private/public) must be passed via parameters');
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'];
251 throw new Exception('Certificate "type" (private/public) must be passed via parameters');
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'];
263 throw new Exception('Certificate "type" (private/public) must be passed via parameters');
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'];
276 throw new Exception('Certificate "type" (private/public) must be passed via parameters');
279 throw new Exception('Invalid Key Type');
285 public function generateSessionKey() {
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) {
293 if ($this->type == XMLSecurityKey::AES256_CBC) {
295 } elseif ($this->type == XMLSecurityKey::AES192_CBC) {
299 while (strlen($key) < $keysize) {
300 $key .= mcrypt_create_iv(mcrypt_enc_get_iv_size ($td),MCRYPT_RAND);
302 mcrypt_module_close($td);
303 $key = substr($key, 0, $keysize);
310 public function loadKey($key, $isFile=FALSE, $isCert = FALSE) {
312 $this->key = file_get_contents($key);
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;
322 $this->x509Certificate = NULL;
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);
332 $this->key = openssl_get_privatekey($this->key, $this->passphrase);
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');
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');
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);
361 $encrypted_data = $this->iv.mcrypt_generic($td, $data);
362 mcrypt_generic_deinit($td);
363 mcrypt_module_close($td);
364 return $encrypted_data;
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);
371 $this->iv = substr($data, 0, $iv_length);
372 $data = substr($data, $iv_length);
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));
383 return $decrypted_data;
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');
393 if (! openssl_private_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
394 throw new Exception('Failure encrypting Data');
398 return $encrypted_data;
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');
408 if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
409 throw new Exception('Failure decrypting Data');
416 private function signOpenSSL($data) {
417 $algo = OPENSSL_ALGO_SHA1;
418 if (! empty($this->cryptParams['digest'])) {
419 $algo = $this->cryptParams['digest'];
421 if (! openssl_sign ($data, $signature, $this->key, $algo)) {
422 throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo);
428 private function verifyOpenSSL($data, $signature) {
429 $algo = OPENSSL_ALGO_SHA1;
430 if (! empty($this->cryptParams['digest'])) {
431 $algo = $this->cryptParams['digest'];
433 return openssl_verify ($data, $signature, $this->key, $algo);
436 public function encryptData($data) {
437 switch ($this->cryptParams['library']) {
439 return $this->encryptMcrypt($data);
442 return $this->encryptOpenSSL($data);
447 public function decryptData($data) {
448 switch ($this->cryptParams['library']) {
450 return $this->decryptMcrypt($data);
453 return $this->decryptOpenSSL($data);
458 public function signData($data) {
459 switch ($this->cryptParams['library']) {
461 return $this->signOpenSSL($data);
466 public function verifySignature($data, $signature) {
467 switch ($this->cryptParams['library']) {
469 return $this->verifyOpenSSL($data, $signature);
474 public function getAlgorith() {
475 return $this->cryptParams['method'];
478 static function makeAsnSegment($type, $string) {
481 if (ord($string) > 0x7f)
482 $string = chr(0).$string;
485 $string = chr(0).$string;
489 $length = strlen($string);
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);
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);
513 /* encode the publicKeyInfo in base64 and add PEM brackets */
514 $publicKeyInfoBase64 = base64_encode($publicKeyInfo);
515 $encoding = "-----BEGIN PUBLIC KEY-----\n";
517 while ($segment=substr($publicKeyInfoBase64, $offset, 64)){
518 $encoding = $encoding.$segment."\n";
521 return $encoding."-----END PUBLIC KEY-----\n";
524 public function serializeKey($parent) {
531 * Retrieve the X509 certificate this key represents.
533 * Will return the X509 certificate in PEM-format if this key represents
534 * an X509 certificate.
536 * @return The X509 certificate or NULL if this key doesn't represent an X509-certificate.
538 public function getX509Certificate() {
539 return $this->x509Certificate;
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';
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';
556 const template = '<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
558 <ds:SignatureMethod />
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';
571 /* This variable contains an associative array of validated nodes. */
572 private $validatedNodes = NULL;
574 public function __construct() {
575 $sigdoc = new DOMDocument();
576 $sigdoc->loadXML(XMLSecurityDSig::template);
577 $this->sigNode = $sigdoc->documentElement;
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;
586 return $this->xPathCtx;
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)."-".
599 public function locateSignature($objDoc) {
600 if ($objDoc instanceof DOMDocument) {
603 $doc = $objDoc->ownerDocument;
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;
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);
621 $node = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, $this->prefix.':'.$name);
626 public function setCanonicalMethod($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;
635 throw new Exception('Invalid Canonical Method');
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);
647 $canonNode->setAttribute('Algorithm', $this->canonicalMethod);
652 private function canonicalizeData($node, $canonicalmethod, $arXPath=NULL, $prefixList=NULL) {
654 $withComments = FALSE;
655 switch ($canonicalmethod) {
656 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
658 $withComments = FALSE;
660 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
661 $withComments = TRUE;
663 case 'http://www.w3.org/2001/10/xml-exc-c14n#':
666 case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
668 $withComments = TRUE;
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");
677 return C14NGeneral($node, $exclusive, $withComments);
679 return $node->C14N($exclusive, $withComments, $arXPath, $prefixList);
682 public function canonicalizeSignedInfo() {
684 $doc = $this->sigNode->ownerDocument;
685 $canonicalmethod = NULL;
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');
696 $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod);
697 return $this->signedInfo;
703 public function calculateDigest ($digestAlgorithm, $data) {
704 switch ($digestAlgorithm) {
705 case XMLSecurityDSig::SHA1:
708 case XMLSecurityDSig::SHA256:
711 case XMLSecurityDSig::SHA512:
714 case XMLSecurityDSig::RIPEMD160:
718 throw new Exception("Cannot validate digest: Unsupported Algorith <$digestAlgorithm>");
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));
728 throw new Exception('xmlseclibs is unable to calculate a digest. Maybe you need the mhash library?');
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);
743 public function processTransforms($refNode, $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';
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;
759 if ($node->localName == 'InclusiveNamespaces') {
760 if ($pfx = $node->getAttribute('PrefixList')) {
762 $pfxlist = preg_split("/\s/", $pfx);
763 foreach ($pfxlist AS $pfx) {
769 if (count($arpfx) > 0) {
770 $prefixList = $arpfx;
775 $node = $node->nextSibling;
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;
781 case 'http://www.w3.org/TR/1999/REC-xpath-19991116':
782 $node = $transform->firstChild;
784 if ($node->localName == 'XPath') {
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;
796 $node = $node->nextSibling;
801 if ($data instanceof DOMNode) {
802 $data = $this->canonicalizeData($objData, $canonicalMethod, $arXPath, $prefixList);
807 public function processRefNode($refNode) {
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);
819 $iDlist = '@Id="'.$identifier.'"';
820 if (is_array($this->idKeys)) {
821 foreach ($this->idKeys AS $idKey) {
822 $iDlist .= " or @$idKey='$identifier'";
825 $query = '//*['.$iDlist.']';
826 $dataObject = $xPath->query($query)->item(0);
828 $dataObject = $refNode->ownerDocument;
831 $dataObject = file_get_contents($arUrl);
834 $dataObject = $refNode->ownerDocument;
836 $data = $this->processTransforms($refNode, $dataObject);
837 if (!$this->validateDigest($refNode, $data)) {
841 if ($dataObject instanceof DOMNode) {
842 /* Add this node to the list of validated nodes. */
843 if(! empty($identifier)) {
844 $this->validatedNodes[$identifier] = $dataObject;
846 $this->validatedNodes[] = $dataObject;
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']) {
865 public function getRefIDs() {
867 $doc = $this->sigNode->ownerDocument;
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");
875 foreach ($nodeset AS $refNode) {
876 $refids[] = $this->getRefNodeID($refNode);
881 public function validateReference() {
882 $doc = $this->sigNode->ownerDocument;
883 if (! $doc->isSameNode($this->sigNode)) {
884 $this->sigNode->parentNode->removeChild($this->sigNode);
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");
893 /* Initialize/reset the list of validated nodes. */
894 $this->validatedNodes = array();
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");
906 private function addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=NULL, $options=NULL) {
910 $overwrite_id = TRUE;
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'];
922 if (! empty($prefix)) {
923 $attname = $prefix.':'.$attname;
926 $refNode = $this->createNewSignNode('Reference');
927 $sinfoNode->appendChild($refNode);
929 if (! $node instanceof DOMDocument) {
931 if (! $overwrite_id) {
932 $uri = $node->getAttributeNS($prefix_ns, $attname);
935 $uri = XMLSecurityDSig::generate_GUID();
936 $node->setAttributeNS($prefix_ns, $attname, $uri);
938 $refNode->setAttribute("URI", '#'.$uri);
939 } elseif ($force_uri) {
940 $refNode->setAttribute("URI", '');
943 $transNodes = $this->createNewSignNode('Transforms');
944 $refNode->appendChild($transNodes);
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);
962 $transNode->setAttribute('Algorithm', $transform);
965 } elseif (! empty($this->canonicalMethod)) {
966 $transNode = $this->createNewSignNode('Transform');
967 $transNodes->appendChild($transNode);
968 $transNode->setAttribute('Algorithm', $this->canonicalMethod);
971 $canonicalData = $this->processTransforms($refNode, $node);
972 $digValue = $this->calculateDigest($algorithm, $canonicalData);
974 $digestMethod = $this->createNewSignNode('DigestMethod');
975 $refNode->appendChild($digestMethod);
976 $digestMethod->setAttribute('Algorithm', $algorithm);
978 $digestValue = $this->createNewSignNode('DigestValue', $digValue);
979 $refNode->appendChild($digestValue);
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);
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);
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);
1010 if (! empty($encoding)) {
1011 $objNode->setAttribute('Encoding', $encoding);
1014 if ($data instanceof DOMElement) {
1015 $newData = $this->sigNode->ownerDocument->importNode($data, TRUE);
1017 $newData = $this->sigNode->ownerDocument->createTextNode($data);
1019 $objNode->appendChild($newData);
1024 public function locateKey($node=NULL) {
1026 $node = $this->sigNode;
1028 if (! $node instanceof DOMNode) {
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);
1038 $objKey = new XMLSecurityKey($algorithm, array('type'=>'public'));
1039 } catch (Exception $e) {
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");
1057 return $objKey->verifySignature($this->signedInfo, base64_decode($sigValue));
1060 public function signData($objKey, $data) {
1061 return $objKey->signData($data);
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);
1079 $this->sigNode->appendChild($sigValueNode);
1085 public function appendCert() {
1089 public function appendKey($objKey, $parent=NULL) {
1090 $objKey->serializeKey($parent);
1095 * This function inserts the signature element.
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.
1100 * @param $node The node the signature element should be inserted into.
1101 * @param $beforeNode The node the signature element should be located before.
1103 public function insertSignature($node, $beforeNode = NULL) {
1105 $document = $node->ownerDocument;
1106 $signatureElement = $document->importNode($this->sigNode, TRUE);
1108 if($beforeNode == NULL) {
1109 $node->insertBefore($signatureElement);
1111 $node->insertBefore($signatureElement, $beforeNode);
1115 public function appendSignature($parentNode, $insertBefore = FALSE) {
1116 $beforeNode = $insertBefore ? $parentNode->firstChild : NULL;
1117 $this->insertSignature($parentNode, $beforeNode);
1120 static function get509XCert($cert, $isPEMFormat=TRUE) {
1121 $certs = XMLSecurityDSig::staticGet509XCerts($cert, $isPEMFormat);
1122 if (! empty($certs)) {
1128 static function staticGet509XCerts($certs, $isPEMFormat=TRUE) {
1131 $certlist = array();
1132 $arCert = explode("\n", $certs);
1134 foreach ($arCert AS $curData) {
1136 if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
1140 if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
1142 $certlist[] = $data;
1146 $data .= trim($curData);
1151 return array($certs);
1155 static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=TRUE, $isURL=False, $xpath=NULL) {
1157 $cert = file_get_contents($cert);
1159 if (! $parentRef instanceof DOMElement) {
1160 throw new Exception('Invalid parent Node parameter');
1162 $baseDoc = $parentRef->ownerDocument;
1164 if (empty($xpath)) {
1165 $xpath = new DOMXPath($parentRef->ownerDocument);
1166 $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1169 $query = "./secdsig:KeyInfo";
1170 $nodeset = $xpath->query($query, $parentRef);
1171 $keyInfo = $nodeset->item(0);
1174 $keyInfo = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:KeyInfo');
1176 $query = "./secdsig:Object";
1177 $nodeset = $xpath->query($query, $parentRef);
1178 if ($sObject = $nodeset->item(0)) {
1179 $sObject->parentNode->insertBefore($keyInfo, $sObject);
1184 $parentRef->appendChild($keyInfo);
1188 // Add all certs if there are more than one
1189 $certs = XMLSecurityDSig::staticGet509XCerts($cert, $isPEMFormat);
1191 // Atach X509 data node
1192 $x509DataNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Data');
1193 $keyInfo->appendChild($x509DataNode);
1195 // Atach all certificate nodes
1196 foreach ($certs as $X509Cert){
1197 $x509CertNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Certificate', $X509Cert);
1198 $x509DataNode->appendChild($x509CertNode);
1202 public function add509Cert($cert, $isPEMFormat=TRUE, $isURL=False) {
1203 if ($xpath = $this->getXPathObj()) {
1204 self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath);
1208 /* This function retrieves an associative array of the validated nodes.
1210 * The array will contain the id of the referenced node as the key and the node itself
1214 * An associative array of validated nodes or NULL if no nodes have been validated.
1216 public function getValidatedNodes() {
1217 return $this->validatedNodes;
1222 const template = "<xenc:EncryptedData xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'>
1224 <xenc:CipherValue></xenc:CipherValue>
1226 </xenc:EncryptedData>";
1228 const Element = 'http://www.w3.org/2001/04/xmlenc#Element';
1229 const Content = 'http://www.w3.org/2001/04/xmlenc#Content';
1231 const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#';
1233 private $encdoc = NULL;
1234 private $rawNode = NULL;
1235 public $type = NULL;
1236 public $encKey = NULL;
1238 public function __construct() {
1239 $this->encdoc = new DOMDocument();
1240 $this->encdoc->loadXML(XMLSecEnc::template);
1243 public function setNode($node) {
1244 $this->rawNode = $node;
1247 public function encryptNode($objKey, $replace=TRUE) {
1249 if (empty($this->rawNode)) {
1250 throw new Exception('Node to encrypt has not been set');
1252 if (! $objKey instanceof XMLSecurityKey) {
1253 throw new Exception('Invalid Key');
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');
1262 switch ($this->type) {
1263 case (XMLSecEnc::Element):
1264 $data = $doc->saveXML($this->rawNode);
1265 $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Element);
1267 case (XMLSecEnc::Content):
1268 $children = $this->rawNode->childNodes;
1269 foreach ($children AS $child) {
1270 $data .= $doc->saveXML($child);
1272 $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Content);
1275 throw new Exception('Type is currently not supported');
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);
1283 $strEncrypt = base64_encode($objKey->encryptData($data));
1284 $value = $this->encdoc->createTextNode($strEncrypt);
1285 $cipherValue->appendChild($value);
1288 switch ($this->type) {
1289 case (XMLSecEnc::Element):
1290 if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1291 return $this->encdoc;
1293 $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, TRUE);
1294 $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
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);
1302 $this->rawNode->appendChild($importEnc);
1309 public function decryptNode($objKey, $replace=TRUE) {
1311 if (empty($this->rawNode)) {
1312 throw new Exception('Node to decrypt has not been set');
1314 if (! $objKey instanceof XMLSecurityKey) {
1315 throw new Exception('Invalid Key');
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);
1324 if ($node = $nodeset->item(0)) {
1325 $encryptedData = base64_decode($node->nodeValue);
1326 $decrypted = $objKey->decryptData($encryptedData);
1328 switch ($this->type) {
1329 case (XMLSecEnc::Element):
1330 $newdoc = new DOMDocument();
1331 $newdoc->loadXML($decrypted);
1332 if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1335 $importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, TRUE);
1336 $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
1339 case (XMLSecEnc::Content):
1340 if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1341 $doc = $this->rawNode;
1343 $doc = $this->rawNode->ownerDocument;
1345 $newFrag = $doc->createDocumentFragment();
1346 $newFrag->appendXML($decrypted);
1347 $parent = $this->rawNode->parentNode;
1348 $parent->replaceChild($newFrag, $this->rawNode);
1358 throw new Exception("Cannot locate encrypted data");
1362 public function encryptKey($srcKey, $rawKey, $append=TRUE) {
1363 if ((! $srcKey instanceof XMLSecurityKey) || (! $rawKey instanceof XMLSecurityKey)) {
1364 throw new Exception('Invalid Key');
1366 $strEncKey = base64_encode($srcKey->encryptData($rawKey->key));
1367 $root = $this->encdoc->documentElement;
1368 $encKey = $this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptedKey');
1370 $keyInfo = $root->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'));
1371 $keyInfo->appendChild($encKey);
1373 $this->encKey = $encKey;
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));
1381 $cipherData = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherData'));
1382 $cipherData->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherValue', $strEncKey));
1386 public function decryptKey($encKey) {
1387 if (! $encKey->isEncrypted) {
1388 throw new Exception("Key is not Encrypted");
1390 if (empty($encKey->key)) {
1391 throw new Exception("Key is missing data to perform the decryption");
1393 return $this->decryptNode($encKey, FALSE);
1396 public function locateEncryptedData($element) {
1397 if ($element instanceof DOMDocument) {
1400 $doc = $element->ownerDocument;
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);
1411 public function locateKey($node=NULL) {
1413 $node = $this->rawNode;
1415 if (! $node instanceof DOMNode) {
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");
1426 $objKey = new XMLSecurityKey($attrAlgorithm, array('type'=>'private'));
1427 } catch (Exception $e) {
1436 static function staticLocateKeyInfo($objBaseKey=NULL, $node=NULL) {
1437 if (empty($node) || (! $node instanceof DOMNode)) {
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) {
1450 if (! empty($objBaseKey)) {
1451 $objBaseKey->name = $child->nodeValue;
1455 foreach ($child->childNodes AS $keyval) {
1456 switch ($keyval->localName) {
1458 throw new Exception("DSAKeyValue currently not supported");
1463 if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) {
1464 $modulus = base64_decode($modulusNode->nodeValue);
1466 if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) {
1467 $exponent = base64_decode($exponentNode->nodeValue);
1469 if (empty($modulus) || empty($exponent)) {
1470 throw new Exception("Missing Modulus or Exponent");
1472 $publicKey = XMLSecurityKey::convertRSA($modulus, $exponent);
1473 $objBaseKey->loadKey($publicKey);
1478 case 'RetrievalMethod':
1479 /* Not currently supported */
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");
1487 $objKey->isEncrypted = TRUE;
1488 $objKey->encryptedCtx = $objenc;
1489 XMLSecEnc::staticLocateKeyInfo($objKey, $child);
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);
1510 public function locateKeyInfo($objBaseKey=NULL, $node=NULL) {
1512 $node = $this->rawNode;
1514 return XMLSecEnc::staticLocateKeyInfo($objBaseKey, $node);