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 $this->key = openssl_get_publickey($this->key);
328 $this->key = openssl_get_privatekey($this->key, $this->passphrase);
330 } else if ($this->cryptParams['cipher'] == MCRYPT_RIJNDAEL_128) {
331 /* Check key length */
332 switch ($this->type) {
333 case (XMLSecurityKey::AES256_CBC):
334 if (strlen($this->key) < 25) {
335 throw new Exception('Key must contain at least 25 characters for this cipher');
338 case (XMLSecurityKey::AES192_CBC):
339 if (strlen($this->key) < 17) {
340 throw new Exception('Key must contain at least 17 characters for this cipher');
347 private function encryptMcrypt($data) {
348 $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
349 $this->iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
350 mcrypt_generic_init($td, $this->key, $this->iv);
351 if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
352 $bs = mcrypt_enc_get_block_size($td);
353 for ($datalen0=$datalen=strlen($data); (($datalen%$bs)!=($bs-1)); $datalen++)
354 $data.=chr(rand(1, 127));
355 $data.=chr($datalen-$datalen0+1);
357 $encrypted_data = $this->iv.mcrypt_generic($td, $data);
358 mcrypt_generic_deinit($td);
359 mcrypt_module_close($td);
360 return $encrypted_data;
363 private function decryptMcrypt($data) {
364 $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
365 $iv_length = mcrypt_enc_get_iv_size($td);
367 $this->iv = substr($data, 0, $iv_length);
368 $data = substr($data, $iv_length);
370 mcrypt_generic_init($td, $this->key, $this->iv);
371 $decrypted_data = mdecrypt_generic($td, $data);
372 mcrypt_generic_deinit($td);
373 mcrypt_module_close($td);
374 if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
375 $dataLen = strlen($decrypted_data);
376 $paddingLength = substr($decrypted_data, $dataLen - 1, 1);
377 $decrypted_data = substr($decrypted_data, 0, $dataLen - ord($paddingLength));
379 return $decrypted_data;
382 private function encryptOpenSSL($data) {
383 if ($this->cryptParams['type'] == 'public') {
384 if (! openssl_public_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
385 throw new Exception('Failure encrypting Data');
389 if (! openssl_private_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
390 throw new Exception('Failure encrypting Data');
394 return $encrypted_data;
397 private function decryptOpenSSL($data) {
398 if ($this->cryptParams['type'] == 'public') {
399 if (! openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
400 throw new Exception('Failure decrypting Data');
404 if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
405 throw new Exception('Failure decrypting Data');
412 private function signOpenSSL($data) {
413 $algo = OPENSSL_ALGO_SHA1;
414 if (! empty($this->cryptParams['digest'])) {
415 $algo = $this->cryptParams['digest'];
417 if (! openssl_sign ($data, $signature, $this->key, $algo)) {
418 throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo);
424 private function verifyOpenSSL($data, $signature) {
425 $algo = OPENSSL_ALGO_SHA1;
426 if (! empty($this->cryptParams['digest'])) {
427 $algo = $this->cryptParams['digest'];
429 return openssl_verify ($data, $signature, $this->key, $algo);
432 public function encryptData($data) {
433 switch ($this->cryptParams['library']) {
435 return $this->encryptMcrypt($data);
438 return $this->encryptOpenSSL($data);
443 public function decryptData($data) {
444 switch ($this->cryptParams['library']) {
446 return $this->decryptMcrypt($data);
449 return $this->decryptOpenSSL($data);
454 public function signData($data) {
455 switch ($this->cryptParams['library']) {
457 return $this->signOpenSSL($data);
462 public function verifySignature($data, $signature) {
463 switch ($this->cryptParams['library']) {
465 return $this->verifyOpenSSL($data, $signature);
470 public function getAlgorith() {
471 return $this->cryptParams['method'];
474 static function makeAsnSegment($type, $string) {
477 if (ord($string) > 0x7f)
478 $string = chr(0).$string;
481 $string = chr(0).$string;
485 $length = strlen($string);
488 $output = sprintf("%c%c%s", $type, $length, $string);
489 } else if ($length < 0x0100){
490 $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string);
491 } else if ($length < 0x010000) {
492 $output = sprintf("%c%c%c%c%s", $type, 0x82, $length/0x0100, $length%0x0100, $string);
499 /* Modulus and Exponent must already be base64 decoded */
500 static function convertRSA($modulus, $exponent) {
501 /* make an ASN publicKeyInfo */
502 $exponentEncoding = XMLSecurityKey::makeAsnSegment(0x02, $exponent);
503 $modulusEncoding = XMLSecurityKey::makeAsnSegment(0x02, $modulus);
504 $sequenceEncoding = XMLSecurityKey:: makeAsnSegment(0x30, $modulusEncoding.$exponentEncoding);
505 $bitstringEncoding = XMLSecurityKey::makeAsnSegment(0x03, $sequenceEncoding);
506 $rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500");
507 $publicKeyInfo = XMLSecurityKey::makeAsnSegment (0x30, $rsaAlgorithmIdentifier.$bitstringEncoding);
509 /* encode the publicKeyInfo in base64 and add PEM brackets */
510 $publicKeyInfoBase64 = base64_encode($publicKeyInfo);
511 $encoding = "-----BEGIN PUBLIC KEY-----\n";
513 while ($segment=substr($publicKeyInfoBase64, $offset, 64)){
514 $encoding = $encoding.$segment."\n";
517 return $encoding."-----END PUBLIC KEY-----\n";
520 public function serializeKey($parent) {
527 * Retrieve the X509 certificate this key represents.
529 * Will return the X509 certificate in PEM-format if this key represents
530 * an X509 certificate.
532 * @return The X509 certificate or NULL if this key doesn't represent an X509-certificate.
534 public function getX509Certificate() {
535 return $this->x509Certificate;
540 class XMLSecurityDSig {
541 const XMLDSIGNS = 'http://www.w3.org/2000/09/xmldsig#';
542 const SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1';
543 const SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256';
544 const SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512';
545 const RIPEMD160 = 'http://www.w3.org/2001/04/xmlenc#ripemd160';
547 const C14N = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
548 const C14N_COMMENTS = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments';
549 const EXC_C14N = 'http://www.w3.org/2001/10/xml-exc-c14n#';
550 const EXC_C14N_COMMENTS = 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments';
552 const template = '<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
554 <ds:SignatureMethod />
558 public $sigNode = NULL;
559 public $idKeys = array();
560 public $idNS = array();
561 private $signedInfo = NULL;
562 private $xPathCtx = NULL;
563 private $canonicalMethod = NULL;
564 private $prefix = 'ds';
565 private $searchpfx = 'secdsig';
567 /* This variable contains an associative array of validated nodes. */
568 private $validatedNodes = NULL;
570 public function __construct() {
571 $sigdoc = new DOMDocument();
572 $sigdoc->loadXML(XMLSecurityDSig::template);
573 $this->sigNode = $sigdoc->documentElement;
576 private function getXPathObj() {
577 if (empty($this->xPathCtx) && ! empty($this->sigNode)) {
578 $xpath = new DOMXPath($this->sigNode->ownerDocument);
579 $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
580 $this->xPathCtx = $xpath;
582 return $this->xPathCtx;
585 static function generate_GUID($prefix='pfx') {
586 $uuid = md5(uniqid(rand(), true));
587 $guid = $prefix.substr($uuid,0,8)."-".
588 substr($uuid,8,4)."-".
589 substr($uuid,12,4)."-".
590 substr($uuid,16,4)."-".
595 public function locateSignature($objDoc) {
596 if ($objDoc instanceof DOMDocument) {
599 $doc = $objDoc->ownerDocument;
602 $xpath = new DOMXPath($doc);
603 $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
604 $query = ".//secdsig:Signature";
605 $nodeset = $xpath->query($query, $objDoc);
606 $this->sigNode = $nodeset->item(0);
607 return $this->sigNode;
612 public function createNewSignNode($name, $value=NULL) {
613 $doc = $this->sigNode->ownerDocument;
614 if (! is_null($value)) {
615 $node = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, $this->prefix.':'.$name, $value);
617 $node = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, $this->prefix.':'.$name);
622 public function setCanonicalMethod($method) {
624 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
625 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
626 case 'http://www.w3.org/2001/10/xml-exc-c14n#':
627 case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
628 $this->canonicalMethod = $method;
631 throw new Exception('Invalid Canonical Method');
633 if ($xpath = $this->getXPathObj()) {
634 $query = './'.$this->searchpfx.':SignedInfo';
635 $nodeset = $xpath->query($query, $this->sigNode);
636 if ($sinfo = $nodeset->item(0)) {
637 $query = './'.$this->searchpfx.'CanonicalizationMethod';
638 $nodeset = $xpath->query($query, $sinfo);
639 if (! ($canonNode = $nodeset->item(0))) {
640 $canonNode = $this->createNewSignNode('CanonicalizationMethod');
641 $sinfo->insertBefore($canonNode, $sinfo->firstChild);
643 $canonNode->setAttribute('Algorithm', $this->canonicalMethod);
648 private function canonicalizeData($node, $canonicalmethod, $arXPath=NULL, $prefixList=NULL) {
650 $withComments = FALSE;
651 switch ($canonicalmethod) {
652 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
654 $withComments = FALSE;
656 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
657 $withComments = TRUE;
659 case 'http://www.w3.org/2001/10/xml-exc-c14n#':
662 case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
664 $withComments = TRUE;
667 /* Support PHP versions < 5.2 not containing C14N methods in DOM extension */
668 $php_version = explode('.', PHP_VERSION);
669 if (($php_version[0] < 5) || ($php_version[0] == 5 && $php_version[1] < 2) ) {
670 if (! is_null($arXPath)) {
671 throw new Exception("PHP 5.2.0 or higher is required to perform XPath Transformations");
673 return C14NGeneral($node, $exclusive, $withComments);
675 return $node->C14N($exclusive, $withComments, $arXPath, $prefixList);
678 public function canonicalizeSignedInfo() {
680 $doc = $this->sigNode->ownerDocument;
681 $canonicalmethod = NULL;
683 $xpath = $this->getXPathObj();
684 $query = "./secdsig:SignedInfo";
685 $nodeset = $xpath->query($query, $this->sigNode);
686 if ($signInfoNode = $nodeset->item(0)) {
687 $query = "./secdsig:CanonicalizationMethod";
688 $nodeset = $xpath->query($query, $signInfoNode);
689 if ($canonNode = $nodeset->item(0)) {
690 $canonicalmethod = $canonNode->getAttribute('Algorithm');
692 $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod);
693 return $this->signedInfo;
699 public function calculateDigest ($digestAlgorithm, $data) {
700 switch ($digestAlgorithm) {
701 case XMLSecurityDSig::SHA1:
704 case XMLSecurityDSig::SHA256:
707 case XMLSecurityDSig::SHA512:
710 case XMLSecurityDSig::RIPEMD160:
714 throw new Exception("Cannot validate digest: Unsupported Algorith <$digestAlgorithm>");
716 if (function_exists('hash')) {
717 return base64_encode(hash($alg, $data, TRUE));
718 } elseif (function_exists('mhash')) {
719 $alg = "MHASH_" . strtoupper($alg);
720 return base64_encode(mhash(constant($alg), $data));
721 } elseif ($alg === 'sha1') {
722 return base64_encode(sha1($data, TRUE));
724 throw new Exception('xmlseclibs is unable to calculate a digest. Maybe you need the mhash library?');
728 public function validateDigest($refNode, $data) {
729 $xpath = new DOMXPath($refNode->ownerDocument);
730 $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
731 $query = 'string(./secdsig:DigestMethod/@Algorithm)';
732 $digestAlgorithm = $xpath->evaluate($query, $refNode);
733 $digValue = $this->calculateDigest($digestAlgorithm, $data);
734 $query = 'string(./secdsig:DigestValue)';
735 $digestValue = $xpath->evaluate($query, $refNode);
736 return ($digValue == $digestValue);
739 public function processTransforms($refNode, $objData) {
741 $xpath = new DOMXPath($refNode->ownerDocument);
742 $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
743 $query = './secdsig:Transforms/secdsig:Transform';
744 $nodelist = $xpath->query($query, $refNode);
745 $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
748 foreach ($nodelist AS $transform) {
749 $algorithm = $transform->getAttribute("Algorithm");
750 switch ($algorithm) {
751 case 'http://www.w3.org/2001/10/xml-exc-c14n#':
752 case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
753 $node = $transform->firstChild;
755 if ($node->localName == 'InclusiveNamespaces') {
756 if ($pfx = $node->getAttribute('PrefixList')) {
758 $pfxlist = preg_split("/\s/", $pfx);
759 foreach ($pfxlist AS $pfx) {
765 if (count($arpfx) > 0) {
766 $prefixList = $arpfx;
771 $node = $node->nextSibling;
773 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
774 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
775 $canonicalMethod = $algorithm;
777 case 'http://www.w3.org/TR/1999/REC-xpath-19991116':
778 $node = $transform->firstChild;
780 if ($node->localName == 'XPath') {
782 $arXPath['query'] = '(.//. | .//@* | .//namespace::*)['.$node->nodeValue.']';
783 $arXpath['namespaces'] = array();
784 $nslist = $xpath->query('./namespace::*', $node);
785 foreach ($nslist AS $nsnode) {
786 if ($nsnode->localName != "xml") {
787 $arXPath['namespaces'][$nsnode->localName] = $nsnode->nodeValue;
792 $node = $node->nextSibling;
797 if ($data instanceof DOMNode) {
798 $data = $this->canonicalizeData($objData, $canonicalMethod, $arXPath, $prefixList);
803 public function processRefNode($refNode) {
805 if ($uri = $refNode->getAttribute("URI")) {
806 $arUrl = parse_url($uri);
807 if (empty($arUrl['path'])) {
808 if ($identifier = $arUrl['fragment']) {
809 $xPath = new DOMXPath($refNode->ownerDocument);
810 if ($this->idNS && is_array($this->idNS)) {
811 foreach ($this->idNS AS $nspf=>$ns) {
812 $xPath->registerNamespace($nspf, $ns);
815 $iDlist = '@Id="'.$identifier.'"';
816 if (is_array($this->idKeys)) {
817 foreach ($this->idKeys AS $idKey) {
818 $iDlist .= " or @$idKey='$identifier'";
821 $query = '//*['.$iDlist.']';
822 $dataObject = $xPath->query($query)->item(0);
824 $dataObject = $refNode->ownerDocument;
827 $dataObject = file_get_contents($arUrl);
830 $dataObject = $refNode->ownerDocument;
832 $data = $this->processTransforms($refNode, $dataObject);
833 if (!$this->validateDigest($refNode, $data)) {
837 if ($dataObject instanceof DOMNode) {
838 /* Add this node to the list of validated nodes. */
839 if(! empty($identifier)) {
840 $this->validatedNodes[$identifier] = $dataObject;
842 $this->validatedNodes[] = $dataObject;
849 public function getRefNodeID($refNode) {
850 if ($uri = $refNode->getAttribute("URI")) {
851 $arUrl = parse_url($uri);
852 if (empty($arUrl['path'])) {
853 if ($identifier = $arUrl['fragment']) {
861 public function getRefIDs() {
863 $doc = $this->sigNode->ownerDocument;
865 $xpath = $this->getXPathObj();
866 $query = "./secdsig:SignedInfo/secdsig:Reference";
867 $nodeset = $xpath->query($query, $this->sigNode);
868 if ($nodeset->length == 0) {
869 throw new Exception("Reference nodes not found");
871 foreach ($nodeset AS $refNode) {
872 $refids[] = $this->getRefNodeID($refNode);
877 public function validateReference() {
878 $doc = $this->sigNode->ownerDocument;
879 if (! $doc->isSameNode($this->sigNode)) {
880 $this->sigNode->parentNode->removeChild($this->sigNode);
882 $xpath = $this->getXPathObj();
883 $query = "./secdsig:SignedInfo/secdsig:Reference";
884 $nodeset = $xpath->query($query, $this->sigNode);
885 if ($nodeset->length == 0) {
886 throw new Exception("Reference nodes not found");
889 /* Initialize/reset the list of validated nodes. */
890 $this->validatedNodes = array();
892 foreach ($nodeset AS $refNode) {
893 if (! $this->processRefNode($refNode)) {
894 /* Clear the list of validated nodes. */
895 $this->validatedNodes = NULL;
896 throw new Exception("Reference validation failed");
902 private function addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=NULL, $options=NULL) {
906 $overwrite_id = TRUE;
909 if (is_array($options)) {
910 $prefix = empty($options['prefix'])?NULL:$options['prefix'];
911 $prefix_ns = empty($options['prefix_ns'])?NULL:$options['prefix_ns'];
912 $id_name = empty($options['id_name'])?'Id':$options['id_name'];
913 $overwrite_id = !isset($options['overwrite'])?TRUE:(bool)$options['overwrite'];
914 $force_uri = !isset($options['force_uri'])?FALSE:(bool)$options['force_uri'];
918 if (! empty($prefix)) {
919 $attname = $prefix.':'.$attname;
922 $refNode = $this->createNewSignNode('Reference');
923 $sinfoNode->appendChild($refNode);
925 if (! $node instanceof DOMDocument) {
927 if (! $overwrite_id) {
928 $uri = $node->getAttributeNS($prefix_ns, $attname);
931 $uri = XMLSecurityDSig::generate_GUID();
932 $node->setAttributeNS($prefix_ns, $attname, $uri);
934 $refNode->setAttribute("URI", '#'.$uri);
935 } elseif ($force_uri) {
936 $refNode->setAttribute("URI", '');
939 $transNodes = $this->createNewSignNode('Transforms');
940 $refNode->appendChild($transNodes);
942 if (is_array($arTransforms)) {
943 foreach ($arTransforms AS $transform) {
944 $transNode = $this->createNewSignNode('Transform');
945 $transNodes->appendChild($transNode);
946 if (is_array($transform) &&
947 (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116'])) &&
948 (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']))) {
949 $transNode->setAttribute('Algorithm', 'http://www.w3.org/TR/1999/REC-xpath-19991116');
950 $XPathNode = $this->createNewSignNode('XPath', $transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']);
951 $transNode->appendChild($XPathNode);
952 if (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'])) {
953 foreach ($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'] AS $prefix => $namespace) {
954 $XPathNode->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:$prefix", $namespace);
958 $transNode->setAttribute('Algorithm', $transform);
961 } elseif (! empty($this->canonicalMethod)) {
962 $transNode = $this->createNewSignNode('Transform');
963 $transNodes->appendChild($transNode);
964 $transNode->setAttribute('Algorithm', $this->canonicalMethod);
967 $canonicalData = $this->processTransforms($refNode, $node);
968 $digValue = $this->calculateDigest($algorithm, $canonicalData);
970 $digestMethod = $this->createNewSignNode('DigestMethod');
971 $refNode->appendChild($digestMethod);
972 $digestMethod->setAttribute('Algorithm', $algorithm);
974 $digestValue = $this->createNewSignNode('DigestValue', $digValue);
975 $refNode->appendChild($digestValue);
978 public function addReference($node, $algorithm, $arTransforms=NULL, $options=NULL) {
979 if ($xpath = $this->getXPathObj()) {
980 $query = "./secdsig:SignedInfo";
981 $nodeset = $xpath->query($query, $this->sigNode);
982 if ($sInfo = $nodeset->item(0)) {
983 $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options);
988 public function addReferenceList($arNodes, $algorithm, $arTransforms=NULL, $options=NULL) {
989 if ($xpath = $this->getXPathObj()) {
990 $query = "./secdsig:SignedInfo";
991 $nodeset = $xpath->query($query, $this->sigNode);
992 if ($sInfo = $nodeset->item(0)) {
993 foreach ($arNodes AS $node) {
994 $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options);
1000 public function addObject($data, $mimetype=NULL, $encoding=NULL) {
1001 $objNode = $this->createNewSignNode('Object');
1002 $this->sigNode->appendChild($objNode);
1003 if (! empty($mimetype)) {
1004 $objNode->setAtribute('MimeType', $mimetype);
1006 if (! empty($encoding)) {
1007 $objNode->setAttribute('Encoding', $encoding);
1010 if ($data instanceof DOMElement) {
1011 $newData = $this->sigNode->ownerDocument->importNode($data, TRUE);
1013 $newData = $this->sigNode->ownerDocument->createTextNode($data);
1015 $objNode->appendChild($newData);
1020 public function locateKey($node=NULL) {
1022 $node = $this->sigNode;
1024 if (! $node instanceof DOMNode) {
1027 if ($doc = $node->ownerDocument) {
1028 $xpath = new DOMXPath($doc);
1029 $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1030 $query = "string(./secdsig:SignedInfo/secdsig:SignatureMethod/@Algorithm)";
1031 $algorithm = $xpath->evaluate($query, $node);
1034 $objKey = new XMLSecurityKey($algorithm, array('type'=>'public'));
1035 } catch (Exception $e) {
1044 public function verify($objKey) {
1045 $doc = $this->sigNode->ownerDocument;
1046 $xpath = new DOMXPath($doc);
1047 $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1048 $query = "string(./secdsig:SignatureValue)";
1049 $sigValue = $xpath->evaluate($query, $this->sigNode);
1050 if (empty($sigValue)) {
1051 throw new Exception("Unable to locate SignatureValue");
1053 return $objKey->verifySignature($this->signedInfo, base64_decode($sigValue));
1056 public function signData($objKey, $data) {
1057 return $objKey->signData($data);
1060 public function sign($objKey) {
1061 if ($xpath = $this->getXPathObj()) {
1062 $query = "./secdsig:SignedInfo";
1063 $nodeset = $xpath->query($query, $this->sigNode);
1064 if ($sInfo = $nodeset->item(0)) {
1065 $query = "./secdsig:SignatureMethod";
1066 $nodeset = $xpath->query($query, $sInfo);
1067 $sMethod = $nodeset->item(0);
1068 $sMethod->setAttribute('Algorithm', $objKey->type);
1069 $data = $this->canonicalizeData($sInfo, $this->canonicalMethod);
1070 $sigValue = base64_encode($this->signData($objKey, $data));
1071 $sigValueNode = $this->createNewSignNode('SignatureValue', $sigValue);
1072 if ($infoSibling = $sInfo->nextSibling) {
1073 $infoSibling->parentNode->insertBefore($sigValueNode, $infoSibling);
1075 $this->sigNode->appendChild($sigValueNode);
1081 public function appendCert() {
1085 public function appendKey($objKey, $parent=NULL) {
1086 $objKey->serializeKey($parent);
1091 * This function inserts the signature element.
1093 * The signature element will be appended to the element, unless $beforeNode is specified. If $beforeNode
1094 * is specified, the signature element will be inserted as the last element before $beforeNode.
1096 * @param $node The node the signature element should be inserted into.
1097 * @param $beforeNode The node the signature element should be located before.
1099 public function insertSignature($node, $beforeNode = NULL) {
1101 $document = $node->ownerDocument;
1102 $signatureElement = $document->importNode($this->sigNode, TRUE);
1104 if($beforeNode == NULL) {
1105 $node->insertBefore($signatureElement);
1107 $node->insertBefore($signatureElement, $beforeNode);
1111 public function appendSignature($parentNode, $insertBefore = FALSE) {
1112 $beforeNode = $insertBefore ? $parentNode->firstChild : NULL;
1113 $this->insertSignature($parentNode, $beforeNode);
1116 static function get509XCert($cert, $isPEMFormat=TRUE) {
1117 $certs = XMLSecurityDSig::staticGet509XCerts($cert, $isPEMFormat);
1118 if (! empty($certs)) {
1124 static function staticGet509XCerts($certs, $isPEMFormat=TRUE) {
1127 $certlist = array();
1128 $arCert = explode("\n", $certs);
1130 foreach ($arCert AS $curData) {
1132 if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
1136 if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
1138 $certlist[] = $data;
1142 $data .= trim($curData);
1147 return array($certs);
1151 static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=TRUE, $isURL=False, $xpath=NULL) {
1153 $cert = file_get_contents($cert);
1155 if (! $parentRef instanceof DOMElement) {
1156 throw new Exception('Invalid parent Node parameter');
1158 $baseDoc = $parentRef->ownerDocument;
1160 if (empty($xpath)) {
1161 $xpath = new DOMXPath($parentRef->ownerDocument);
1162 $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
1165 $query = "./secdsig:KeyInfo";
1166 $nodeset = $xpath->query($query, $parentRef);
1167 $keyInfo = $nodeset->item(0);
1170 $keyInfo = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:KeyInfo');
1172 $query = "./secdsig:Object";
1173 $nodeset = $xpath->query($query, $parentRef);
1174 if ($sObject = $nodeset->item(0)) {
1175 $sObject->parentNode->insertBefore($keyInfo, $sObject);
1180 $parentRef->appendChild($keyInfo);
1184 // Add all certs if there are more than one
1185 $certs = XMLSecurityDSig::staticGet509XCerts($cert, $isPEMFormat);
1187 // Atach X509 data node
1188 $x509DataNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Data');
1189 $keyInfo->appendChild($x509DataNode);
1191 // Atach all certificate nodes
1192 foreach ($certs as $X509Cert){
1193 $x509CertNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Certificate', $X509Cert);
1194 $x509DataNode->appendChild($x509CertNode);
1198 public function add509Cert($cert, $isPEMFormat=TRUE, $isURL=False) {
1199 if ($xpath = $this->getXPathObj()) {
1200 self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath);
1204 /* This function retrieves an associative array of the validated nodes.
1206 * The array will contain the id of the referenced node as the key and the node itself
1210 * An associative array of validated nodes or NULL if no nodes have been validated.
1212 public function getValidatedNodes() {
1213 return $this->validatedNodes;
1218 const template = "<xenc:EncryptedData xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'>
1220 <xenc:CipherValue></xenc:CipherValue>
1222 </xenc:EncryptedData>";
1224 const Element = 'http://www.w3.org/2001/04/xmlenc#Element';
1225 const Content = 'http://www.w3.org/2001/04/xmlenc#Content';
1227 const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#';
1229 private $encdoc = NULL;
1230 private $rawNode = NULL;
1231 public $type = NULL;
1232 public $encKey = NULL;
1234 public function __construct() {
1235 $this->encdoc = new DOMDocument();
1236 $this->encdoc->loadXML(XMLSecEnc::template);
1239 public function setNode($node) {
1240 $this->rawNode = $node;
1243 public function encryptNode($objKey, $replace=TRUE) {
1245 if (empty($this->rawNode)) {
1246 throw new Exception('Node to encrypt has not been set');
1248 if (! $objKey instanceof XMLSecurityKey) {
1249 throw new Exception('Invalid Key');
1251 $doc = $this->rawNode->ownerDocument;
1252 $xPath = new DOMXPath($this->encdoc);
1253 $objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue');
1254 $cipherValue = $objList->item(0);
1255 if ($cipherValue == NULL) {
1256 throw new Exception('Error locating CipherValue element within template');
1258 switch ($this->type) {
1259 case (XMLSecEnc::Element):
1260 $data = $doc->saveXML($this->rawNode);
1261 $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Element);
1263 case (XMLSecEnc::Content):
1264 $children = $this->rawNode->childNodes;
1265 foreach ($children AS $child) {
1266 $data .= $doc->saveXML($child);
1268 $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Content);
1271 throw new Exception('Type is currently not supported');
1275 $encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptionMethod'));
1276 $encMethod->setAttribute('Algorithm', $objKey->getAlgorith());
1277 $cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode);
1279 $strEncrypt = base64_encode($objKey->encryptData($data));
1280 $value = $this->encdoc->createTextNode($strEncrypt);
1281 $cipherValue->appendChild($value);
1284 switch ($this->type) {
1285 case (XMLSecEnc::Element):
1286 if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1287 return $this->encdoc;
1289 $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, TRUE);
1290 $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
1293 case (XMLSecEnc::Content):
1294 $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, TRUE);
1295 while($this->rawNode->firstChild) {
1296 $this->rawNode->removeChild($this->rawNode->firstChild);
1298 $this->rawNode->appendChild($importEnc);
1305 public function decryptNode($objKey, $replace=TRUE) {
1307 if (empty($this->rawNode)) {
1308 throw new Exception('Node to decrypt has not been set');
1310 if (! $objKey instanceof XMLSecurityKey) {
1311 throw new Exception('Invalid Key');
1313 $doc = $this->rawNode->ownerDocument;
1314 $xPath = new DOMXPath($doc);
1315 $xPath->registerNamespace('xmlencr', XMLSecEnc::XMLENCNS);
1316 /* Only handles embedded content right now and not a reference */
1317 $query = "./xmlencr:CipherData/xmlencr:CipherValue";
1318 $nodeset = $xPath->query($query, $this->rawNode);
1320 if ($node = $nodeset->item(0)) {
1321 $encryptedData = base64_decode($node->nodeValue);
1322 $decrypted = $objKey->decryptData($encryptedData);
1324 switch ($this->type) {
1325 case (XMLSecEnc::Element):
1326 $newdoc = new DOMDocument();
1327 $newdoc->loadXML($decrypted);
1328 if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1331 $importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, TRUE);
1332 $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
1335 case (XMLSecEnc::Content):
1336 if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
1337 $doc = $this->rawNode;
1339 $doc = $this->rawNode->ownerDocument;
1341 $newFrag = $doc->createDocumentFragment();
1342 $newFrag->appendXML($decrypted);
1343 $parent = $this->rawNode->parentNode;
1344 $parent->replaceChild($newFrag, $this->rawNode);
1354 throw new Exception("Cannot locate encrypted data");
1358 public function encryptKey($srcKey, $rawKey, $append=TRUE) {
1359 if ((! $srcKey instanceof XMLSecurityKey) || (! $rawKey instanceof XMLSecurityKey)) {
1360 throw new Exception('Invalid Key');
1362 $strEncKey = base64_encode($srcKey->encryptData($rawKey->key));
1363 $root = $this->encdoc->documentElement;
1364 $encKey = $this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptedKey');
1366 $keyInfo = $root->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'));
1367 $keyInfo->appendChild($encKey);
1369 $this->encKey = $encKey;
1371 $encMethod = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptionMethod'));
1372 $encMethod->setAttribute('Algorithm', $srcKey->getAlgorith());
1373 if (! empty($srcKey->name)) {
1374 $keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'));
1375 $keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name));
1377 $cipherData = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherData'));
1378 $cipherData->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherValue', $strEncKey));
1382 public function decryptKey($encKey) {
1383 if (! $encKey->isEncrypted) {
1384 throw new Exception("Key is not Encrypted");
1386 if (empty($encKey->key)) {
1387 throw new Exception("Key is missing data to perform the decryption");
1389 return $this->decryptNode($encKey, FALSE);
1392 public function locateEncryptedData($element) {
1393 if ($element instanceof DOMDocument) {
1396 $doc = $element->ownerDocument;
1399 $xpath = new DOMXPath($doc);
1400 $query = "//*[local-name()='EncryptedData' and namespace-uri()='".XMLSecEnc::XMLENCNS."']";
1401 $nodeset = $xpath->query($query);
1402 return $nodeset->item(0);
1407 public function locateKey($node=NULL) {
1409 $node = $this->rawNode;
1411 if (! $node instanceof DOMNode) {
1414 if ($doc = $node->ownerDocument) {
1415 $xpath = new DOMXPath($doc);
1416 $xpath->registerNamespace('xmlsecenc', XMLSecEnc::XMLENCNS);
1417 $query = ".//xmlsecenc:EncryptionMethod";
1418 $nodeset = $xpath->query($query, $node);
1419 if ($encmeth = $nodeset->item(0)) {
1420 $attrAlgorithm = $encmeth->getAttribute("Algorithm");
1422 $objKey = new XMLSecurityKey($attrAlgorithm, array('type'=>'private'));
1423 } catch (Exception $e) {
1432 static function staticLocateKeyInfo($objBaseKey=NULL, $node=NULL) {
1433 if (empty($node) || (! $node instanceof DOMNode)) {
1436 if ($doc = $node->ownerDocument) {
1437 $xpath = new DOMXPath($doc);
1438 $xpath->registerNamespace('xmlsecenc', XMLSecEnc::XMLENCNS);
1439 $xpath->registerNamespace('xmlsecdsig', XMLSecurityDSig::XMLDSIGNS);
1440 $query = "./xmlsecdsig:KeyInfo";
1441 $nodeset = $xpath->query($query, $node);
1442 if ($encmeth = $nodeset->item(0)) {
1443 foreach ($encmeth->childNodes AS $child) {
1444 switch ($child->localName) {
1446 if (! empty($objBaseKey)) {
1447 $objBaseKey->name = $child->nodeValue;
1451 foreach ($child->childNodes AS $keyval) {
1452 switch ($keyval->localName) {
1454 throw new Exception("DSAKeyValue currently not supported");
1459 if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) {
1460 $modulus = base64_decode($modulusNode->nodeValue);
1462 if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) {
1463 $exponent = base64_decode($exponentNode->nodeValue);
1465 if (empty($modulus) || empty($exponent)) {
1466 throw new Exception("Missing Modulus or Exponent");
1468 $publicKey = XMLSecurityKey::convertRSA($modulus, $exponent);
1469 $objBaseKey->loadKey($publicKey);
1474 case 'RetrievalMethod':
1475 /* Not currently supported */
1477 case 'EncryptedKey':
1478 $objenc = new XMLSecEnc();
1479 $objenc->setNode($child);
1480 if (! $objKey = $objenc->locateKey()) {
1481 throw new Exception("Unable to locate algorithm for this Encrypted Key");
1483 $objKey->isEncrypted = TRUE;
1484 $objKey->encryptedCtx = $objenc;
1485 XMLSecEnc::staticLocateKeyInfo($objKey, $child);
1489 if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) {
1490 if ($x509certNodes->length > 0) {
1491 $x509cert = $x509certNodes->item(0)->textContent;
1492 $x509cert = str_replace(array("\r", "\n"), "", $x509cert);
1493 $x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n";
1494 $objBaseKey->loadKey($x509cert, FALSE, TRUE);
1506 public function locateKeyInfo($objBaseKey=NULL, $node=NULL) {
1508 $node = $this->rawNode;
1510 return XMLSecEnc::staticLocateKeyInfo($objBaseKey, $node);