From 97b3ffa254543833a6dcb19de00c6f485b08ff85 Mon Sep 17 00:00:00 2001
From: Olav Morken <olav.morken@uninett.no>
Date: Thu, 27 Oct 2011 09:17:26 +0000
Subject: [PATCH] Minimal backport of key oracle fixes.

This is a backport of the changes in r2953, with one small change
from r2952.

git-svn-id: https://simplesamlphp.googlecode.com/svn/branches/simplesamlphp-1.8@2955 44740490-163a-0410-bde0-09ae8108e29a
---
 lib/SAML2/Utils.php | 35 ++++++++++++++++++++++++++++++++++-
 lib/xmlseclibs.php  | 41 ++++++++++++++++++++++++++++++++++-------
 2 files changed, 68 insertions(+), 8 deletions(-)

diff --git a/lib/SAML2/Utils.php b/lib/SAML2/Utils.php
index df08db622..8cc872639 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 c74b3b2b7..a7efb688a 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) {
-- 
GitLab