diff --git a/lib/SAML2/Utils.php b/lib/SAML2/Utils.php index df08db62214b221828bc0358af12ba08c8d43b62..8cc872639c935ac3acabdc88ba9c29c192cc0280 100644 --- a/lib/SAML2/Utils.php +++ b/lib/SAML2/Utils.php @@ -344,8 +344,41 @@ class SAML2_Utils { $encKey = $symmetricKeyInfo->encryptedCtx; $symmetricKeyInfo->key = $inputKey->key; - $key = $encKey->decryptKey($symmetricKeyInfo); + + $keySizes = array( + XMLSecurityKey::TRIPLEDES_CBC => 24, + XMLSecurityKey::AES128_CBC => 16, + XMLSecurityKey::AES192_CBC => 24, + XMLSecurityKey::AES256_CBC => 32, + ); + if (!isset($keySizes[$symmetricKey->type])) { + /* To protect against "key oracle" attacks, we need to be able to create a + * symmetric key, and for that we need to know the key size. + */ + throw new Exception('Unsupported encryption algorithm: ' . var_export($symmetricKey->type, TRUE)); + } + $keySize = $keySizes[$symmetricKey->type]; + + try { + $key = $encKey->decryptKey($symmetricKeyInfo); + } catch (Exception $e) { + /* We failed to decrypt this key. Log it, and substitute a "random" key. */ + SimpleSAML_Logger::error('Failed to decrypt symmetric key: ' . $e->getMessage()); + /* Create a replacement key, so that it looks like we fail in the same way as if the key was correctly padded. */ + + /* We base the symmetric key on the encrypted key, so that we always behave the same way for a given input key. */ + $encryptedKey = $encKey->getCipherValue(); + $key = md5($encryptedKey, TRUE); + + /* Make sure that the key has the correct length. */ + if (strlen($key) > $keySize) { + $key = substr($key, 0, $keySize); + } elseif (strlen($key) < $keySize) { + $key = str_pad($key, $keySize); + } + } $symmetricKey->loadkey($key); + } else { $symKeyAlgo = $symmetricKey->getAlgorith(); /* Make sure that the input key has the correct format. */ diff --git a/lib/xmlseclibs.php b/lib/xmlseclibs.php index c74b3b2b72324d6826e8f384a17b7867e2546c7d..a7efb688af062a09396a56d353f0174a044c8a6b 100644 --- a/lib/xmlseclibs.php +++ b/lib/xmlseclibs.php @@ -1454,23 +1454,50 @@ class XMLSecEnc { $this->type = $curType; } - public function decryptNode($objKey, $replace=TRUE) { - $data = ''; + /** + * 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'); } - if (! $objKey instanceof XMLSecurityKey) { - throw new Exception('Invalid Key'); - } + $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'); + } - if ($node = $nodeset->item(0)) { - $encryptedData = base64_decode($node->nodeValue); + $encryptedData = $this->getCipherValue(); + if ($encryptedData) { $decrypted = $objKey->decryptData($encryptedData); if ($replace) { switch ($this->type) {