/** * Decrypts an encrypted element. * * @param DOMElement $encryptedData The encrypted data. * @param XMLSecurityKey $inputKey The decryption key. * * @return DOMElement The decrypted element. */ public static function decryptElement(DOMElement $encryptedData, XMLSecurityKey $inputKey) { $enc = new XMLSecEnc(); $enc->setNode($encryptedData); $enc->type = $encryptedData->getAttribute("Type"); $symmetricKey = $enc->locateKey($encryptedData); if (!$symmetricKey) { throw new Exception('Could not locate key algorithm in encrypted data.'); } $symmetricKeyInfo = $enc->locateKeyInfo($symmetricKey); if (!$symmetricKeyInfo) { throw new Exception('Could not locate <dsig:KeyInfo> for the encrypted key.'); } $inputKeyAlgo = $inputKey->getAlgorith(); if ($symmetricKeyInfo->isEncrypted) { $symKeyInfoAlgo = $symmetricKeyInfo->getAlgorith(); if ($symKeyInfoAlgo === XMLSecurityKey::RSA_OAEP_MGF1P && $inputKeyAlgo === XMLSecurityKey::RSA_1_5) { $inputKeyAlgo = XMLSecurityKey::RSA_OAEP_MGF1P; } if ($inputKeyAlgo !== $symKeyInfoAlgo) { throw new Exception('Algorithm mismatch between input key and key used to encrypt ' . ' the symmetric key for the message. Key was: ' . var_export($inputKeyAlgo, true) . '; message was: ' . var_export($symKeyInfoAlgo, true)); } $encKey = $symmetricKeyInfo->encryptedCtx; $symmetricKeyInfo->key = $inputKey->key; $keySize = $symmetricKey->getSymmetricKeySize(); if ($keySize === null) { // To protect against "key oracle" attacks throw new Exception('Unknown key size for encryption algorithm: ' . var_export($symmetricKey->type, true)); } $key = $encKey->decryptKey($symmetricKeyInfo); if (strlen($key) != $keySize) { $encryptedKey = $encKey->getCipherValue(); $pkey = openssl_pkey_get_details($symmetricKeyInfo->key); $pkey = sha1(serialize($pkey), true); $key = sha1($encryptedKey . $pkey, 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(); if ($inputKeyAlgo !== $symKeyAlgo) { throw new Exception('Algorithm mismatch between input key and key in message. ' . 'Key was: ' . var_export($inputKeyAlgo, true) . '; message was: ' . var_export($symKeyAlgo, true)); } $symmetricKey = $inputKey; } $decrypted = $enc->decryptNode($symmetricKey, false); $xml = '<root xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' . $decrypted . '</root>'; $newDoc = new DOMDocument(); $newDoc->preserveWhiteSpace = false; $newDoc->formatOutput = true; $newDoc = self::loadXML($newDoc, $xml); if (!$newDoc) { throw new Exception('Failed to parse decrypted XML.'); } $decryptedElement = $newDoc->firstChild->firstChild; if ($decryptedElement === null) { throw new Exception('Missing encrypted element.'); } return $decryptedElement; }
/** * Decrypts an encrypted element. * * @param DOMElement $encryptedData The encrypted data. * @param XMLSecurityKey $inputKey The decryption key. * * @return DOMElement The decrypted element. */ public static function decryptElement(DOMElement $encryptedData, XMLSecurityKey $inputKey) { $enc = new XMLSecEnc(); $enc->setNode($encryptedData); $enc->type = $encryptedData->getAttribute("Type"); $symmetricKey = $enc->locateKey($encryptedData); if (!$symmetricKey) { throw new Exception('Could not locate key algorithm in encrypted data.'); } $symmetricKeyInfo = $enc->locateKeyInfo($symmetricKey); if (!$symmetricKeyInfo) { throw new Exception('Could not locate <dsig:KeyInfo> for the encrypted key.'); } $inputKeyAlgo = $inputKey->getAlgorith(); if ($symmetricKeyInfo->isEncrypted) { $symKeyInfoAlgo = $symmetricKeyInfo->getAlgorith(); if ($symKeyInfoAlgo === XMLSecurityKey::RSA_OAEP_MGF1P && $inputKeyAlgo === XMLSecurityKey::RSA_1_5) { $inputKeyAlgo = XMLSecurityKey::RSA_OAEP_MGF1P; } if ($inputKeyAlgo !== $symKeyInfoAlgo) { throw new Exception('Algorithm mismatch between input key and key used to encrypt ' . ' the symmetric key for the message. Key was: ' . var_export($inputKeyAlgo, true) . '; message was: ' . var_export($symKeyInfoAlgo, true)); } $encKey = $symmetricKeyInfo->encryptedCtx; $symmetricKeyInfo->key = $inputKey->key; $key = $encKey->decryptKey($symmetricKeyInfo); $symmetricKey->loadkey($key); } else { $symKeyAlgo = $symmetricKey->getAlgorith(); if ($inputKeyAlgo !== $symKeyAlgo) { throw new Exception('Algorithm mismatch between input key and key in message. ' . 'Key was: ' . var_export($inputKeyAlgo, true) . '; message was: ' . var_export($symKeyAlgo, true)); } $symmetricKey = $inputKey; } $decrypted = $enc->decryptNode($symmetricKey, false); $xml = '<root xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' . $decrypted . '</root>'; $newDoc = new DOMDocument(); $newDoc = self::loadXML($newDoc, $xml); if (!$newDoc) { throw new Exception('Failed to parse decrypted XML. Maybe the wrong sharedkey was used?'); } $decryptedElement = $newDoc->firstChild->firstChild; if ($decryptedElement === null) { throw new Exception('Missing encrypted element.'); } return $decryptedElement; }
/** * Decrypt an encrypted element. * * This is an internal helper function. * * @param DOMElement $encryptedData The encrypted data. * @param XMLSecurityKey $inputKey The decryption key. * @param array &$blacklist Blacklisted decryption algorithms. * @return DOMElement The decrypted element. * @throws Exception */ private static function doDecryptElement(DOMElement $encryptedData, XMLSecurityKey $inputKey, array &$blacklist) { $enc = new XMLSecEnc(); $enc->setNode($encryptedData); $enc->type = $encryptedData->getAttribute("Type"); $symmetricKey = $enc->locateKey($encryptedData); if (!$symmetricKey) { throw new Exception('Could not locate key algorithm in encrypted data.'); } $symmetricKeyInfo = $enc->locateKeyInfo($symmetricKey); if (!$symmetricKeyInfo) { throw new Exception('Could not locate <dsig:KeyInfo> for the encrypted key.'); } $inputKeyAlgo = $inputKey->getAlgorith(); if ($symmetricKeyInfo->isEncrypted) { $symKeyInfoAlgo = $symmetricKeyInfo->getAlgorith(); if (in_array($symKeyInfoAlgo, $blacklist, TRUE)) { throw new Exception('Algorithm disabled: ' . var_export($symKeyInfoAlgo, TRUE)); } if ($symKeyInfoAlgo === XMLSecurityKey::RSA_OAEP_MGF1P && $inputKeyAlgo === XMLSecurityKey::RSA_1_5) { /* * The RSA key formats are equal, so loading an RSA_1_5 key * into an RSA_OAEP_MGF1P key can be done without problems. * We therefore pretend that the input key is an * RSA_OAEP_MGF1P key. */ $inputKeyAlgo = XMLSecurityKey::RSA_OAEP_MGF1P; } /* Make sure that the input key format is the same as the one used to encrypt the key. */ if ($inputKeyAlgo !== $symKeyInfoAlgo) { throw new Exception('Algorithm mismatch between input key and key used to encrypt ' . ' the symmetric key for the message. Key was: ' . var_export($inputKeyAlgo, TRUE) . '; message was: ' . var_export($symKeyInfoAlgo, TRUE)); } /** @var XMLSecEnc $encKey */ $encKey = $symmetricKeyInfo->encryptedCtx; $symmetricKeyInfo->key = $inputKey->key; $keySize = $symmetricKey->getSymmetricKeySize(); if ($keySize === NULL) { /* 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('Unknown key size for encryption algorithm: ' . var_export($symmetricKey->type, TRUE)); } try { $key = $encKey->decryptKey($symmetricKeyInfo); if (strlen($key) != $keySize) { throw new Exception('Unexpected key size (' . strlen($key) * 8 . 'bits) for encryption algorithm: ' . var_export($symmetricKey->type, TRUE)); } } catch (Exception $e) { /* We failed to decrypt this key. Log it, and substitute a "random" key. */ SAML2_Utils::getContainer()->getLogger()->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 and private key, so that we always behave the * same way for a given input key. */ $encryptedKey = $encKey->getCipherValue(); $pkey = openssl_pkey_get_details($symmetricKeyInfo->key); $pkey = sha1(serialize($pkey), TRUE); $key = sha1($encryptedKey . $pkey, 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. */ if ($inputKeyAlgo !== $symKeyAlgo) { throw new Exception('Algorithm mismatch between input key and key in message. ' . 'Key was: ' . var_export($inputKeyAlgo, TRUE) . '; message was: ' . var_export($symKeyAlgo, TRUE)); } $symmetricKey = $inputKey; } $algorithm = $symmetricKey->getAlgorith(); if (in_array($algorithm, $blacklist, TRUE)) { throw new Exception('Algorithm disabled: ' . var_export($algorithm, TRUE)); } /** @var string $decrypted */ $decrypted = $enc->decryptNode($symmetricKey, FALSE); /* * This is a workaround for the case where only a subset of the XML * tree was serialized for encryption. In that case, we may miss the * namespaces needed to parse the XML. */ $xml = '<root xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ' . 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' . $decrypted . '</root>'; $newDoc = new DOMDocument(); if (!@$newDoc->loadXML($xml)) { throw new Exception('Failed to parse decrypted XML. Maybe the wrong sharedkey was used?'); } $decryptedElement = $newDoc->firstChild->firstChild; if ($decryptedElement === NULL) { throw new Exception('Missing encrypted element.'); } if (!$decryptedElement instanceof DOMElement) { throw new Exception('Decrypted element was not actually a DOMElement.'); } return $decryptedElement; }
/** * Decrypts the Assertion (DOMDocument) * * @param string $dom DomDocument * * @throws Exception * @return DOMDocument Decrypted Assertion */ private function _decryptAssertion($dom) { $pem = $this->_settings->getSPkey(); if (empty($pem)) { throw new Exception("No private key available, check settings"); } $objenc = new XMLSecEnc(); $encData = $objenc->locateEncryptedData($dom); if (!$encData) { throw new Exception("Cannot locate encrypted assertion"); } $objenc->setNode($encData); $objenc->type = $encData->getAttribute("Type"); if (!($objKey = $objenc->locateKey())) { throw new Exception("Unknown algorithm"); } $key = null; if ($objKeyInfo = $objenc->locateKeyInfo($objKey)) { if ($objKeyInfo->isEncrypted) { $objencKey = $objKeyInfo->encryptedCtx; $objKeyInfo->loadKey($pem, false, false); $key = $objencKey->decryptKey($objKeyInfo); } } if (empty($objKey->key)) { $objKey->loadKey($key); } $decrypt = $objenc->decryptNode($objKey, true); if ($decrypt instanceof DOMDocument) { return $decrypt; } else { return $decrypt->ownerDocument; } }
/** * Decrypt an encrypted element. * * @param DOMElement $encryptedData The encrypted data. * @param XMLSecurityKey $inputKey The decryption key. * @return DOMElement The decrypted element. */ public static function decryptElement(DOMElement $encryptedData, XMLSecurityKey $inputKey) { $enc = new XMLSecEnc(); $enc->setNode($encryptedData); $enc->type = $encryptedData->getAttribute("Type"); $symmetricKey = $enc->locateKey($encryptedData); if (!$symmetricKey) { throw new Exception('Could not locate key algorithm in encrypted data.'); } $symmetricKeyInfo = $enc->locateKeyInfo($symmetricKey); if (!$symmetricKeyInfo) { throw new Exception('Could not locate <dsig:KeyInfo> for the encrypted key.'); } $inputKeyAlgo = $inputKey->getAlgorith(); if ($symmetricKeyInfo->isEncrypted) { $symKeyInfoAlgo = $symmetricKeyInfo->getAlgorith(); if ($symKeyInfoAlgo === XMLSecurityKey::RSA_OAEP_MGF1P && $inputKeyAlgo === XMLSecurityKey::RSA_1_5) { /* * The RSA key formats are equal, so loading an RSA_1_5 key * into an RSA_OAEP_MGF1P key can be done without problems. * We therefore pretend that the input key is an * RSA_OAEP_MGF1P key. */ $inputKeyAlgo = XMLSecurityKey::RSA_OAEP_MGF1P; } /* Make sure that the input key format is the same as the one used to encrypt the key. */ if ($inputKeyAlgo !== $symKeyInfoAlgo) { throw new Exception('Algorithm mismatch between input key and key used to encrypt ' . ' the symmetric key for the message. Key was: ' . var_export($inputKeyAlgo, TRUE) . '; message was: ' . var_export($symKeyInfoAlgo, TRUE)); } $encKey = $symmetricKeyInfo->encryptedCtx; $symmetricKeyInfo->key = $inputKey->key; $key = $encKey->decryptKey($symmetricKeyInfo); $symmetricKey->loadkey($key); } else { $symKeyAlgo = $symmetricKey->getAlgorith(); /* Make sure that the input key has the correct format. */ if ($inputKeyAlgo !== $symKeyAlgo) { throw new Exception('Algorithm mismatch between input key and key in message. ' . 'Key was: ' . var_export($inputKeyAlgo, TRUE) . '; message was: ' . var_export($symKeyAlgo, TRUE)); } $symmetricKey = $inputKey; } $decrypted = $enc->decryptNode($symmetricKey, FALSE); /* * This is a workaround for the case where only a subset of the XML * tree was serialized for encryption. In that case, we may miss the * namespaces needed to parse the XML. */ $xml = '<root xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' . $decrypted . '</root>'; $newDoc = new DOMDocument(); if (!$newDoc->loadXML($xml)) { throw new Exception('Failed to parse decrypted XML. Maybe the wrong sharedkey was used?'); } $decryptedElement = $newDoc->firstChild->firstChild; if ($decryptedElement === NULL) { throw new Exception('Missing encrypted element.'); } return $decryptedElement; }
/** * This function decrypts the Assertion in the AuthnResponse * It throws an exception if the encryptAssertion for the remote idp is true and * the assertion is not encrypted * To Do: handle multible assertions */ private function decryptAssertion() { $dom = $this->getDOM(); $encryptedassertion = $this->doXPathQuery('/samlp:Response/saml:EncryptedAssertion')->item(0); $objenc = new XMLSecEnc(); $encData = $objenc->locateEncryptedData($dom); if ($encData) { $spmd = $this->metadata->getMetaDataCurrent('saml20-sp-hosted'); $spid = $this->metadata->getMetaDataCurrentEntityID('saml20-sp-hosted'); $objenc->setNode($encData); $objenc->type = $encData->getAttribute("Type"); $key = NULL; $objKey = $objenc->locateKey($encData); if ($objKey) { if ($objKeyInfo = $objenc->locateKeyInfo($objKey)) { if ($objKeyInfo->isEncrypted) { $objencKey = $objKeyInfo->encryptedCtx; if (empty($spmd['privatekey'])) { throw new Exception("SAML: RSA private key not configured. This is required to decrypt the response. saml20-sp-hosted: {$spid}"); } $objKeyInfo->loadKey($spmd['privatekey']); $key = $objencKey->decryptKey($objKeyInfo); } else { $idpmd = $this->metadata->getMetaData($this->issuer, 'saml20-idp-remote'); if (!isset($idpmd['sharedkey'])) { throw new Exception("Shared key for decrypting assertion needed, but not specified for saml20-idp-remote id: " . $this->issuer); } $key = $idpmd['sharedkey']; } } } if (empty($objKey) || empty($key)) { throw new Exception("Error loading key to handle Decryption: >" . var_export($objKey, true)); } $objKey->loadkey($key); $decrypted = $objenc->decryptNode($objKey, false); $newdoc = new DOMDocument(); $newdoc->loadXML('<root xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' . $decrypted . '</root>'); $importEnc = $encData->ownerDocument->importNode($newdoc->documentElement->firstChild, TRUE); $encryptedassertion->parentNode->replaceChild($importEnc, $encryptedassertion); } else { $md = $this->metadata->getMetaData($this->issuer, 'saml20-idp-remote'); if (isset($md['assertion.encryption']) && $md['assertion.encryption']) { throw new Exception('Received unencrypted assertion from [' . $this->issuer . '] contrary to its metadata attribute [assertion.encryption]: ' . $md['assertion.encryption']); } } }
/** * Decrypts the Assertion (DOMDocument) * * @param DomNode $dom DomDocument * * @return DOMDocument Decrypted Assertion * * @throws Exception */ protected function _decryptAssertion($dom) { $pem = $this->_settings->getSPkey(); if (empty($pem)) { throw new Exception("No private key available, check settings"); } $objenc = new XMLSecEnc(); $encData = $objenc->locateEncryptedData($dom); if (!$encData) { throw new Exception("Cannot locate encrypted assertion"); } $objenc->setNode($encData); $objenc->type = $encData->getAttribute("Type"); if (!($objKey = $objenc->locateKey())) { throw new Exception("Unknown algorithm"); } $key = null; if ($objKeyInfo = $objenc->locateKeyInfo($objKey)) { if ($objKeyInfo->isEncrypted) { $objencKey = $objKeyInfo->encryptedCtx; $objKeyInfo->loadKey($pem, false, false); $key = $objencKey->decryptKey($objKeyInfo); } else { // symmetric encryption key support $objKeyInfo->loadKey($pem, false, false); } } if (empty($objKey->key)) { $objKey->loadKey($key); } $decrypted = $objenc->decryptNode($objKey, true); if ($decrypted instanceof DOMDocument) { return $decrypted; } else { $encryptedAssertion = $decrypted->parentNode; $container = $encryptedAssertion->parentNode; # Fix possible issue with saml namespace if (!$decrypted->hasAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:saml') && !$decrypted->hasAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:saml2') && !$decrypted->hasAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns') && !$container->hasAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:saml') && !$container->hasAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:saml2')) { if (strpos($encryptedAssertion->tagName, 'saml2:') !== false) { $ns = 'xmlns:saml2'; } else { if (strpos($encryptedAssertion->tagName, 'saml:') != false) { $ns = 'xmlns:saml'; } else { $ns = 'xmlns'; } } $decrypted->setAttributeNS('http://www.w3.org/2000/xmlns/', $ns, OneLogin_Saml2_Constants::NS_SAML); } $container->replaceChild($decrypted, $encryptedAssertion); return $decrypted->ownerDocument; } }
/** * * @@dataProvider decryptFilesProvider * @throws \Exception */ public function testDecrypt($testName, $testFile, $privKey) { $doc = new \DOMDocument(); $output = null; $doc->load($testFile); $objenc = new XMLSecEnc(); $encData = $objenc->locateEncryptedData($doc); $this->assertInstanceOf('\\DOMElement', $encData, "Cannot locate Encrypted Data"); $objenc->setNode($encData); $objenc->type = $encData->getAttribute("Type"); $objKey = $objenc->locateKey(); $this->assertInstanceOf('\\XmlSecLibs\\XMLSecurityKey', $objKey, "We know the secret key, but not the algorithm"); $key = null; if ($objKeyInfo = $objenc->locateKeyInfo($objKey)) { if ($objKeyInfo->isEncrypted) { $objencKey = $objKeyInfo->encryptedCtx; $objKeyInfo->loadKey($privKey, true); $key = $objencKey->decryptKey($objKeyInfo); } } if (!$objKey->key && empty($key)) { $objKeyInfo->loadKey($privKey, true); } if (empty($objKey->key)) { $objKey->loadKey($key); } $token = null; if ($decrypt = $objenc->decryptNode($objKey, true)) { $output = null; if ($decrypt instanceof \DOMNode) { if ($decrypt instanceof \DOMDocument) { $output = $decrypt->saveXML(); } else { $output = $decrypt->ownerDocument->saveXML(); } } else { $output = $decrypt; } } $outfile = dirname(__FILE__) . "/../basic-doc.xml"; $res = null; $this->assertFileExists($outfile); $resDoc = new \DOMDocument(); $resDoc->load($outfile); $res = $resDoc->saveXML(); $this->assertEquals($res, $output, "{$testName} Failed to decrypt {$testFile}"); }