class XMLSecEnc in SAML SP 2.0 Single Sign On (SSO) - SAML Service Provider 7
Hierarchy
- class \XMLSecEnc
Expanded class hierarchy of XMLSecEnc
File
- includes/
XMLSecurityKey.php, line 1853
View source
class XMLSecEnc {
const template = "<xenc:EncryptedData xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'>\n <xenc:CipherData>\n <xenc:CipherValue></xenc:CipherValue>\n </xenc:CipherData>\n</xenc:EncryptedData>";
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#';
/** @var null|DOMDocument */
private $encdoc = null;
/** @var null|DOMNode */
private $rawNode = null;
/** @var null|string */
public $type = null;
/** @var null|DOMElement */
public $encKey = null;
/** @var array */
private $references = array();
public function __construct() {
$this
->_resetTemplate();
}
private function _resetTemplate() {
$this->encdoc = new DOMDocument();
$this->encdoc
->loadXML(self::template);
}
/**
* @param string $name
* @param DOMNode $node
* @param string $type
* @throws Exception
*/
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::generateGUID();
$element = $encdoc->documentElement;
$element
->setAttribute("Id", $refuri);
$this->references[$name] = array(
"node" => $node,
"type" => $type,
"encnode" => $encdoc,
"refuri" => $refuri,
);
}
/**
* @param DOMNode $node
*/
public function setNode($node) {
$this->rawNode = $node;
}
/**
* Encrypt the selected node with the given key.
*
* @param XMLSecurityKey $objKey The encryption key and algorithm.
* @param bool $replace Whether the encrypted node should be replaced in the original tree. Default is true.
* @return DOMElement The <xenc:EncryptedData>-element.
* @throws Exception
*
*/
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 self::Element:
$data = $doc
->saveXML($this->rawNode);
$this->encdoc->documentElement
->setAttribute('Type', self::Element);
break;
case self::Content:
$children = $this->rawNode->childNodes;
foreach ($children as $child) {
$data .= $doc
->saveXML($child);
}
$this->encdoc->documentElement
->setAttribute('Type', self::Content);
break;
default:
throw new Exception('Type is currently not supported');
}
$encMethod = $this->encdoc->documentElement
->appendChild($this->encdoc
->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod'));
$encMethod
->setAttribute('Algorithm', $objKey
->getAlgorithm());
$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 self::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;
case self::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;
}
}
else {
return $this->encdoc->documentElement;
}
}
/**
* @param XMLSecurityKey $objKey
* @throws Exception
*/
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.
* @throws Exception
*/
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', self::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).
*
* @param XMLSecurityKey $objKey The decryption key that should be used when decrypting the node.
* @param 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 self::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;
case self::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;
default:
return $decrypted;
}
}
else {
return $decrypted;
}
}
else {
throw new Exception("Cannot locate encrypted data");
}
}
/**
* Encrypt the XMLSecurityKey
*
* @param XMLSecurityKey $srcKey
* @param XMLSecurityKey $rawKey
* @param bool $append
* @throws Exception
*/
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(self::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(self::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(self::XMLENCNS, 'xenc:CipherData'));
$cipherData
->appendChild($this->encdoc
->createElementNS(self::XMLENCNS, 'xenc:CipherValue', $strEncKey));
if (is_array($this->references) && count($this->references) > 0) {
$refList = $encKey
->appendChild($this->encdoc
->createElementNS(self::XMLENCNS, 'xenc:ReferenceList'));
foreach ($this->references as $name => $reference) {
$refuri = $reference["refuri"];
$dataRef = $refList
->appendChild($this->encdoc
->createElementNS(self::XMLENCNS, 'xenc:DataReference'));
$dataRef
->setAttribute("URI", '#' . $refuri);
}
}
return;
}
/**
* @param XMLSecurityKey $encKey
* @return DOMElement|string
* @throws Exception
*/
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);
}
/**
* @param DOMDocument $element
* @return DOMNode|null
*/
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()='" . self::XMLENCNS . "']";
$nodeset = $xpath
->query($query);
return $nodeset
->item(0);
}
return null;
}
/**
* Returns the key from the DOM
* @param null|DOMNode $node
* @return null|XMLSecurityKey
*/
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', self::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;
}
/**
* @param null|XMLSecurityKey $objBaseKey
* @param null|DOMNode $node
* @return null|XMLSecurityKey
* @throws Exception
*/
public 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', self::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");
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="' . XPath::filterAttrValue($id, XPath::DOUBLE_QUOTE) . '"]';
$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;
}
/**
* @param null|XMLSecurityKey $objBaseKey
* @param null|DOMNode $node
* @return null|XMLSecurityKey
*/
public function locateKeyInfo($objBaseKey = null, $node = null) {
if (empty($node)) {
$node = $this->rawNode;
}
return self::staticLocateKeyInfo($objBaseKey, $node);
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
XMLSecEnc:: |
private | property | @var null|DOMDocument | |
XMLSecEnc:: |
public | property | @var null|DOMElement | |
XMLSecEnc:: |
private | property | @var null|DOMNode | |
XMLSecEnc:: |
private | property | @var array | |
XMLSecEnc:: |
public | property | @var null|string | |
XMLSecEnc:: |
public | function | ||
XMLSecEnc:: |
constant | |||
XMLSecEnc:: |
public | function | ||
XMLSecEnc:: |
public | function | Decrypt this encrypted node. | |
XMLSecEnc:: |
constant | |||
XMLSecEnc:: |
public | function | Encrypt the XMLSecurityKey | |
XMLSecEnc:: |
public | function | Encrypt the selected node with the given key. | |
XMLSecEnc:: |
public | function | ||
XMLSecEnc:: |
public | function | Retrieve the CipherValue text from this encrypted node. | |
XMLSecEnc:: |
public | function | ||
XMLSecEnc:: |
public | function | Returns the key from the DOM | |
XMLSecEnc:: |
public | function | ||
XMLSecEnc:: |
public | function | ||
XMLSecEnc:: |
public static | function | ||
XMLSecEnc:: |
constant | |||
XMLSecEnc:: |
constant | |||
XMLSecEnc:: |
constant | |||
XMLSecEnc:: |
private | function | ||
XMLSecEnc:: |
public | function |