tyDSig::staticGet509XCerts($cert, $isPEMFormat); // Atach X509 data node $x509DataNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Data'); $keyInfo->appendChild($x509DataNode); // Atach all certificate nodes foreach ($certs as $X509Cert) { $x509CertNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Certificate', $X509Cert); $x509DataNode->appendChild($x509CertNode); } } public function add509Cert($cert, $isPEMFormat=TRUE, $isURL=False) { if ($xpath = $this->getXPathObj()) { self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath); } } function addKeyInfoAndName($keyName, $xpath=NULL) { $baseDoc = $this->sigNode->ownerDocument; if (empty($xpath)) { $xpath = new DOMXPath($baseDoc); $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS); } $query = "./secdsig:KeyInfo"; $nodeset = $xpath->query($query, $this->sigNode); $keyInfo = $nodeset->item(0); if (!$keyInfo) { $inserted = FALSE; $keyInfo = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'KeyInfo'); $query = "./secdsig:Object"; $nodeset = $xpath->query($query, $this->sigNode); if ($sObject = $nodeset->item(0)) { $sObject->parentNode->insertBefore($keyInfo, $sObject); $inserted = TRUE; } if (!$inserted) { $this->sigNode->appendChild($keyInfo); } } $keyInfo->appendChild($baseDoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'KeyName', $keyName)); } /* This function retrieves an associative array of the validated nodes. * * The array will contain the id of the referenced node as the key and the node itself * as the value. * * Returns: * An associative array of validated nodes or NULL if no nodes have been validated. */ public function getValidatedNodes() { return $this->validatedNodes; } } class XMLSecEnc { const template = " "; const Element = 'http://www.w3.org/2001/04/xmlenc#Element'; const Content = 'http://www.w3.org/2001/04/xmlenc#Content'; const URI = 3; const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#'; private $encdoc = NULL; private $rawNode = NULL; public $type = NULL; public $encKey = NULL; private $references = array(); public function __construct() { $this->_resetTemplate(); } private function _resetTemplate() { $this->encdoc = new DOMDocument(); $this->encdoc->loadXML(XMLSecEnc::template); } public function addReference($name, $node, $type) { if (!$node instanceOf DOMNode) { throw new Exception('$node is not of type DOMNode'); } $curencdoc = $this->encdoc; $this->_resetTemplate(); $encdoc = $this->encdoc; $this->encdoc = $curencdoc; $refuri = XMLSecurityDSig::generate_GUID(); $element = $encdoc->documentElement; $element->setAttribute("Id", $refuri); $this->references[$name] = array("node" => $node, "type" => $type, "encnode" => $encdoc, "refuri" => $refuri); } public function setNode($node) { $this->rawNode = $node; } public function encryptNode($objKey, $replace=TRUE) { $data = ''; if (empty($this->rawNode)) { throw new Exception('Node to encrypt has not been set'); } if (!$objKey instanceof XMLSecurityKey) { throw new Exception('Invalid Key'); } $doc = $this->rawNode->ownerDocument; $xPath = new DOMXPath($this->encdoc); $objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue'); $cipherValue = $objList->item(0); if ($cipherValue == NULL) { throw new Exception('Error locating CipherValue element within template'); } switch ($this->type) { case (XMLSecEnc::Element): $data = $doc->saveXML($this->rawNode); $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Element); break; case (XMLSecEnc::Content): $children = $this->rawNode->childNodes; foreach ($children AS $child) { $data .= $doc->saveXML($child); } $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Content); break; default: throw new Exception('Type is currently not supported'); return; } $encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptionMethod')); $encMethod->setAttribute('Algorithm', $objKey->getAlgorith()); $cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode->parentNode->firstChild); $strEncrypt = base64_encode($objKey->encryptData($data)); $value = $this->encdoc->createTextNode($strEncrypt); $cipherValue->appendChild($value); if ($replace) { switch ($this->type) { case (XMLSecEnc::Element): if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { return $this->encdoc; } $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, TRUE); $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode); return $importEnc; break; case (XMLSecEnc::Content): $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, TRUE); while ($this->rawNode->firstChild) { $this->rawNode->removeChild($this->rawNode->firstChild); } $this->rawNode->appendChild($importEnc); return $importEnc; break; } } } public function encryptReferences($objKey) { $curRawNode = $this->rawNode; $curType = $this->type; foreach ($this->references AS $name => $reference) { $this->encdoc = $reference["encnode"]; $this->rawNode = $reference["node"]; $this->type = $reference["type"]; try { $encNode = $this->encryptNode($objKey); $this->references[$name]["encnode"] = $encNode; } catch (Exception $e) { $this->rawNode = $curRawNode; $this->type = $curType; throw $e; } } $this->rawNode = $curRawNode; $this->type = $curType; } /** * Retrieve the CipherValue text from this encrypted node. * * @return string|NULL The Ciphervalue text, or NULL if no CipherValue is found. */ public function getCipherValue() { if (empty($this->rawNode)) { throw new Exception('Node to decrypt has not been set'); } $doc = $this->rawNode->ownerDocument; $xPath = new DOMXPath($doc); $xPath->registerNamespace('xmlencr', XMLSecEnc::XMLENCNS); /* Only handles embedded content right now and not a reference */ $query = "./xmlencr:CipherData/xmlencr:CipherValue"; $nodeset = $xPath->query($query, $this->rawNode); $node = $nodeset->item(0); if (!$node) { return NULL; } return base64_decode($node->nodeValue); } /** * Decrypt this encrypted node. * * The behaviour of this function depends on the value of $replace. * If $replace is FALSE, we will return the decrypted data as a string. * If $replace is TRUE, we will insert the decrypted element(s) into the * document, and return the decrypted element(s). * * @params XMLSecurityKey $objKey The decryption key that should be used when decrypting the node. * @params boolean $replace Whether we should replace the encrypted node in the XML document with the decrypted data. The default is TRUE. * @return string|DOMElement The decrypted data. */ public function decryptNode($objKey, $replace=TRUE) { if (!$objKey instanceof XMLSecurityKey) { throw new Exception('Invalid Key'); } $encryptedData = $this->getCipherValue(); if ($encryptedData) { $decrypted = $objKey->decryptData($encryptedData); if ($replace) { switch ($this->type) { case (XMLSecEnc::Element): $newdoc = new DOMDocument(); $newdoc->loadXML($decrypted); if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { return $newdoc; } $importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, TRUE); $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode); return $importEnc; break; case (XMLSecEnc::Content): if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { $doc = $this->rawNode; } else { $doc = $this->rawNode->ownerDocument; } $newFrag = $doc->createDocumentFragment(); $newFrag->appendXML($decrypted); $parent = $this->rawNode->parentNode; $parent->replaceChild($newFrag, $this->rawNode); return $parent; break; default: return $decrypted; } } else { return $decrypted; } } else { throw new Exception("Cannot locate encrypted data"); } } public function encryptKey($srcKey, $rawKey, $append=TRUE) { if ((!$srcKey instanceof XMLSecurityKey) || (!$rawKey instanceof XMLSecurityKey)) { throw new Exception('Invalid Key'); } $strEncKey = base64_encode($srcKey->encryptData($rawKey->key)); $root = $this->encdoc->documentElement; $encKey = $this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptedKey'); if ($append) { $keyInfo = $root->insertBefore($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'), $root->firstChild); $keyInfo->appendChild($encKey); } else { $this->encKey = $encKey; } $encMethod = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptionMethod')); $encMethod->setAttribute('Algorithm', $srcKey->getAlgorith()); if (!empty($srcKey->name)) { $keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo')); $keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name)); } $cipherData = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherData')); $cipherData->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherValue', $strEncKey)); if (is_array($this->references) && count($this->references) > 0) { $refList = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:ReferenceList')); foreach ($this->references AS $name => $reference) { $refuri = $reference["refuri"]; $dataRef = $refList->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:DataReference')); $dataRef->setAttribute("URI", '#' . $refuri); } } return; } public function decryptKey($encKey) { if (!$encKey->isEncrypted) { throw new Exception("Key is not Encrypted"); } if (empty($encKey->key)) { throw new Exception("Key is missing data to perform the decryption"); } return $this->decryptNode($encKey, FALSE); } public function locateEncryptedData($element) { if ($element instanceof DOMDocument) { $doc = $element; } else { $doc = $element->ownerDocument; } if ($doc) { $xpath = new DOMXPath($doc); $query = "//*[local-name()='EncryptedData' and namespace-uri()='" . XMLSecEnc::XMLENCNS . "']"; $nodeset = $xpath->query($query); return $nodeset->item(0); } return NULL; } public function locateKey($node=NULL) { if (empty($node)) { $node = $this->rawNode; } if (!$node instanceof DOMNode) { return NULL; } if ($doc = $node->ownerDocument) { $xpath = new DOMXPath($doc); $xpath->registerNamespace('xmlsecenc', XMLSecEnc::XMLENCNS); $query = ".//xmlsecenc:EncryptionMethod"; $nodeset = $xpath->query($query, $node); if ($encmeth = $nodeset->item(0)) { $attrAlgorithm = $encmeth->getAttribute("Algorithm"); try { $objKey = new XMLSecurityKey($attrAlgorithm, array('type' => 'private')); } catch (Exception $e) { return NULL; } return $objKey; } } return NULL; } static function staticLocateKeyInfo($objBaseKey=NULL, $node=NULL) { if (empty($node) || (!$node instanceof DOMNode)) { return NULL; } $doc = $node->ownerDocument; if (!$doc) { return NULL; } $xpath = new DOMXPath($doc); $xpath->registerNamespace('xmlsecenc', XMLSecEnc::XMLENCNS); $xpath->registerNamespace('xmlsecdsig', XMLSecurityDSig::XMLDSIGNS); $query = "./xmlsecdsig:KeyInfo"; $nodeset = $xpath->query($query, $node); $encmeth = $nodeset->item(0); if (!$encmeth) { /* No KeyInfo in EncryptedData / EncryptedKey. */ return $objBaseKey; } foreach ($encmeth->childNodes AS $child) { switch ($child->localName) { case 'KeyName': if (!empty($objBaseKey)) { $objBaseKey->name = $child->nodeValue; } break; case 'KeyValue': foreach ($child->childNodes AS $keyval) { switch ($keyval->localName) { case 'DSAKeyValue': throw new Exception("DSAKeyValue currently not supported"); break; case 'RSAKeyValue': $modulus = NULL; $exponent = NULL; if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) { $modulus = base64_decode($modulusNode->nodeValue); } if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) { $exponent = base64_decode($exponentNode->nodeValue); } if (empty($modulus) || empty($exponent)) { throw new Exception("Missing Modulus or Exponent"); } $publicKey = XMLSecurityKey::convertRSA($modulus, $exponent); $objBaseKey->loadKey($publicKey); break; } } break; case 'RetrievalMethod': $type = $child->getAttribute('Type'); if ($type !== 'http://www.w3.org/2001/04/xmlenc#EncryptedKey') { /* Unsupported key type. */ break; } $uri = $child->getAttribute('URI'); if ($uri[0] !== '#') { /* URI not a reference - unsupported. */ break; } $id = substr($uri, 1); $query = "//xmlsecenc:EncryptedKey[@Id='$id']"; $keyElement = $xpath->query($query)->item(0); if (!$keyElement) { throw new Exception("Unable to locate EncryptedKey with @Id='$id'."); } return XMLSecurityKey::fromEncryptedKeyElement($keyElement); case 'EncryptedKey': return XMLSecurityKey::fromEncryptedKeyElement($child); case 'X509Data': if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) { if ($x509certNodes->length > 0) { $x509cert = $x509certNodes->item(0)->textContent; $x509cert = str_replace(array("\r", "\n"), "", $x509cert); $x509cert = "-----BEGIN CERTIFICATE-----\n" . chunk_split($x509cert, 64, "\n") . "-----END CERTIFICATE-----\n"; $objBaseKey->loadKey($x509cert, FALSE, TRUE); } } break; } } return $objBaseKey; } public function locateKeyInfo($objBaseKey=NULL, $node=NULL) { if (empty($node)) { $node = $this->rawNode; } return XMLSecEnc::staticLocateKeyInfo($objBaseKey, $node); } }