diff --git a/docs/source/simplesamlphp-idp.xml b/docs/source/simplesamlphp-idp.xml index 4ae2351b1fb2b6863b599c586e50df208ed2648c..f0a9f8fe24936b5b95695f17c280bc5bfa20dd10 100644 --- a/docs/source/simplesamlphp-idp.xml +++ b/docs/source/simplesamlphp-idp.xml @@ -570,6 +570,27 @@ openssl x509 -req -days 60 -in server2.csr -signkey server2.key -out server2.crt is <literal>false</literal>.</para> </glossdef> </glossentry> + + <glossentry> + <glossterm>assertion.enctyption</glossterm> + + <glossdef> + <para>Boolean, default <literal>false</literal>. Defines whether + this IdP shoul encrypt assertions to this SP.</para> + </glossdef> + </glossentry> + + <glossentry> + <glossterm>sharedkey</glossterm> + + <glossdef> + <para>Used for optional symmetric encryption of assertions. + Currently the algorithm is hardcoded to AES128_CBC/RIJNDAEL_128 + which uses up to 128 bit for the passphrase/key. If you want to + use a sessionkey which is encrypted by this idp's private key do + not specify a sharedkey.</para> + </glossdef> + </glossentry> </glosslist> </section> </section> diff --git a/docs/source/simplesamlphp-sp.xml b/docs/source/simplesamlphp-sp.xml index 1552150aa40a9dbab9ecab2af92cb98ecc8d54ea..76d0ccc99ac069ae84686db36a3332cf7c4d507b 100644 --- a/docs/source/simplesamlphp-sp.xml +++ b/docs/source/simplesamlphp-sp.xml @@ -447,6 +447,30 @@ validating signatures.</para> </glossdef> </glossentry> + + <glossentry> + <glossterm>assertion.encryption</glossterm> + + <glossdef> + <para>Set to true if the remote idp encrypts assertions. + Encrypted assertions are decrypted regardless of the value this + field if the neccesary certificate or key is available, but if + it is set to true and an unencrypted assertion is recieved an + exception is raised.</para> + </glossdef> + </glossentry> + + <glossentry> + <glossterm>sharedkey</glossterm> + + <glossdef> + <para>Used for optional symmetric decryption of assertions. + Currently the algorithm is hardcoded to AES128_CBC/RIJNDAEL_128 + which uses up to 128 bit for the passphrase/key. Not necessary + if the idp uses a sessionkey which is encrypted by the idp's + private key.</para> + </glossdef> + </glossentry> </glosslist> </section> diff --git a/lib/SimpleSAML/Bindings/SAML20/HTTPPost.php b/lib/SimpleSAML/Bindings/SAML20/HTTPPost.php index e79559b959969ef0e1f266fa7692eb28bc93a2b4..21db2627c45fb0aa9efc12556b831bb26cceacf9 100644 --- a/lib/SimpleSAML/Bindings/SAML20/HTTPPost.php +++ b/lib/SimpleSAML/Bindings/SAML20/HTTPPost.php @@ -125,11 +125,7 @@ class SimpleSAML_Bindings_SAML20_HTTPPost { /* load the private key from file - last arg is bool if key in file (TRUE) or is string (FALSE) */ $objKey->loadKey($privatekey,TRUE); - - - - - + $objXMLSecDSig->sign($objKey); $public_cert = file_get_contents($publiccert); @@ -146,6 +142,43 @@ class SimpleSAML_Bindings_SAML20_HTTPPost { $objXMLSecDSig->appendSignature($firstassertionroot, true, true); //$objXMLSecDSig->appendSignature($responseroot, true, false); + if (isset($spmd['assertion.encryption']) && $spmd['assertion.encryption']) { + $encryptedassertion = $responsedom->createElement("saml:EncryptedAssertion"); + $encryptedassertion->setAttribute("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion"); + + $firstassertionroot->parentNode->replaceChild($encryptedassertion, $firstassertionroot); + $encryptedassertion->appendChild($firstassertionroot); + + $enc = new XMLSecEnc(); + $enc->setNode($firstassertionroot); + $enc->type = XMLSecEnc::Element; + + $objKey = new XMLSecurityKey(XMLSecurityKey::AES128_CBC); + if (isset($spmd['sharedkey'])) { + $objKey->loadkey($spmd['sharedkey']); + } else { + $key = $objKey->generateSessionKey(); + $objKey->loadKey($key); + + if (!isset($spmd['certificate'])) { + throw new Exception("Public key for encrypting assertion needed, but not specified for saml20-sp-remote id: " . $spentityid); + } + + $sp_publiccert = @file_get_contents($this->configuration->getPathValue('certdir') . $spmd['certificate']); + + if ($sp_publiccert === FALSE) { + throw new Exception("Public key for encrypting assertion specified but not found for saml20-sp-remote id: " . $spentityid . " Filename: " . $spmd['certificate']); + } + + $keyKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'public')); + + $keyKey->loadKey($sp_publiccert); + + $enc->encryptKey($keyKey, $objKey); + } + $encNode = $enc->encryptNode($objKey); # replacing the unencrypted node + + } $response = $responsedom->saveXML(); diff --git a/lib/SimpleSAML/XML/SAML20/AuthnResponse.php b/lib/SimpleSAML/XML/SAML20/AuthnResponse.php index d97ee857b844413aa861ac487dd2d5a5eebbe008..76b4669fa505af3be43ab3c7a15a77926070f88e 100644 --- a/lib/SimpleSAML/XML/SAML20/AuthnResponse.php +++ b/lib/SimpleSAML/XML/SAML20/AuthnResponse.php @@ -167,6 +167,68 @@ class SimpleSAML_XML_SAML20_AuthnResponse extends SimpleSAML_XML_AuthnResponse { } + /** + * 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 (!isset($spmd['privatekey'])) { + throw new Exception("Private key for decrypting assertion needed, but not specified for saml20-sp-hosted id: " . $spid); + } + $privatekey = @file_get_contents($this->configuration->getPathValue('certdir') . $spmd['privatekey']); + if ($privatekey === FALSE) { + throw new Exception("Private key for decrypting assertion specified but not found for saml20-sp-hosted id: " . $spid . " Filename: " . $spmd['privatekey']); + } + $objKeyInfo->loadKey($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']); + } + } + } + /** * This function validates the signature element of this response. It will throw an exception * if it is unable to validate the signature. @@ -180,7 +242,7 @@ class SimpleSAML_XML_SAML20_AuthnResponse extends SimpleSAML_XML_AuthnResponse { $publickey = FALSE; if (isset($md['certificate'])) { - $publickey = file_get_contents($this->configuration->getPathValue('certdir') . $md['certificate']); + $publickey = @file_get_contents($this->configuration->getPathValue('certdir') . $md['certificate']); if (!$publickey) { throw new Exception("Optional saml20-idp-remote metadata 'certificate' set, but no certificate found"); } @@ -408,6 +470,8 @@ class SimpleSAML_XML_SAML20_AuthnResponse extends SimpleSAML_XML_AuthnResponse { /* Find the issuer of this response. */ $this->issuer = $this->findIssuer(); + $this->decryptAssertion(); + /* Validate the signature element. */ $this->validateSignature(); @@ -531,6 +595,8 @@ class SimpleSAML_XML_SAML20_AuthnResponse extends SimpleSAML_XML_AuthnResponse { $authnResponse = '<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ID="' . $id . '" InResponseTo="' . htmlspecialchars($inresponseto) . '" Version="2.0" IssueInstant="' . $issueInstant . '" @@ -606,7 +672,7 @@ class SimpleSAML_XML_SAML20_AuthnResponse extends SimpleSAML_XML_AuthnResponse { private static function enc_attribute($name, $values, $base64 = false) { assert(is_array($values)); - $ret = '<saml:Attribute Name="' . htmlspecialchars($name) . '">'; + $ret = '<saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="' . htmlspecialchars($name) . '">'; foreach($values as $value) { if($base64) { @@ -615,7 +681,7 @@ class SimpleSAML_XML_SAML20_AuthnResponse extends SimpleSAML_XML_AuthnResponse { $text = htmlspecialchars($value); } - $ret .= '<saml:AttributeValue xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">' . + $ret .= '<saml:AttributeValue xsi:type="xs:string">' . $text . '</saml:AttributeValue>'; } diff --git a/lib/xmlseclibs.php b/lib/xmlseclibs.php index 7e65d0be81c7599a5bc83492f2b9ba17cd475c99..6892306eb09a0295cf9c979f56ae194cff9483e9 100644 --- a/lib/xmlseclibs.php +++ b/lib/xmlseclibs.php @@ -258,6 +258,7 @@ class XMLSecurityKey { case (XMLSecurityKey::RSA_SHA1): $this->cryptParams['library'] = 'openssl'; $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; if (is_array($params) && ! empty($params['type'])) { if ($params['type'] == 'public' || $params['type'] == 'private') { $this->cryptParams['type'] = $params['type']; @@ -415,7 +416,7 @@ class XMLSecurityKey { private function encryptOpenSSL($data) { if ($this->cryptParams['type'] == 'public') { if (! openssl_public_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) { - throw new Exception('Failure encrypting Data'); + throw new Exception('Failure encrypting Data ' . openssl_error_string() . "###" . $this->key); return; } } else { @@ -435,7 +436,7 @@ class XMLSecurityKey { } } else { if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) { - throw new Exception('Failure decrypting Data'); + throw new Exception('Failure decrypting Data' . openssl_error_string() . "###" . $this->key); return; } } diff --git a/www/admin/metadata.php b/www/admin/metadata.php index 1070a3e22e1a22a11fb84cc23e665727866e0757..c690214458f5f251a20e286cca54ef2e8ec8b67a 100644 --- a/www/admin/metadata.php +++ b/www/admin/metadata.php @@ -44,7 +44,7 @@ try { foreach ($metalist AS $entityid => $mentry) { $results[$entityid] = SimpleSAML_Utilities::checkAssocArrayRules($mentry, array('entityid', 'SingleSignOnService', 'SingleLogoutService', 'certFingerprint'), - array('name', 'description', 'base64attributes', 'certificate', 'hint.cidr', 'saml2.relaxvalidation', 'SingleLogoutServiceResponse', 'request.signing', 'attributemap', 'attributealter') + array('name', 'description', 'base64attributes', 'certificate', 'hint.cidr', 'saml2.relaxvalidation', 'SingleLogoutServiceResponse', 'request.signing', 'attributemap', 'attributealter', 'sharedkey', 'assertion.encryption') ); } $et->data['metadata.saml20-idp-remote'] = $results; @@ -67,7 +67,7 @@ try { foreach ($metalist AS $entityid => $mentry) { $results[$entityid] = SimpleSAML_Utilities::checkAssocArrayRules($mentry, array('entityid', 'AssertionConsumerService'), - array('SingleLogoutService', 'NameIDFormat', 'SPNameQualifier', 'base64attributes', 'simplesaml.nameidattribute', 'attributemap', 'attributealter', 'simplesaml.attributes', 'attributes', 'name', 'description','request.signing','certificate', 'ForceAuthn') + array('SingleLogoutService', 'NameIDFormat', 'SPNameQualifier', 'base64attributes', 'simplesaml.nameidattribute', 'attributemap', 'attributealter', 'simplesaml.attributes', 'attributes', 'name', 'description','request.signing','certificate', 'ForceAuthn', 'sharedkey', 'assertion.encryption') ); } $et->data['metadata.saml20-sp-remote'] = $results;