diff --git a/modules/saml2/lib/Message.php b/modules/saml2/lib/Message.php
index c332a3693f4beaaeb9774ac79ba312e9c976518d..1f8c9a39a913db3f035011c6c914810a9215efae 100644
--- a/modules/saml2/lib/Message.php
+++ b/modules/saml2/lib/Message.php
@@ -74,6 +74,179 @@ class sspmod_saml2_Message {
 	}
 
 
+	/**
+	 * Find the certificate used to sign a message or assertion.
+	 *
+	 * An exception is thrown if we are unable to locate the certificate.
+	 *
+	 * @param array $certFingerprints  The fingerprints we are looking for.
+	 * @param array $certificates  Array of certificates.
+	 * @return string  Certificate, in PEM-format.
+	 */
+	private static function findCertificate(array $certFingerprints, array $certificates) {
+
+		$candidates = array();
+
+		foreach ($certificates as $cert) {
+			$fp = strtolower(sha1(base64_decode($cert)));
+			if (!in_array($fp, $certFingerprints, TRUE)) {
+				$candidates[] = $fp;
+				continue;
+			}
+
+			/* We have found a matching fingerprint. */
+			$pem = "-----BEGIN CERTIFICATE-----\n" .
+				chunk_split($cert, 64) .
+				"-----END CERTIFICATE-----\n";
+			return $pem;
+		}
+
+		$candidates = "'" . implode("', '", $candidates) . "'";
+		$fps = "'" .  implode("', '", $certFingerprints) . "'";
+		throw new SimpleSAML_Error_Exception('Unable to find a certificate matching the configured ' .
+			'fingerprint. Candidates: ' . $candidates . '; certFingerprint: ' . $fps . '.');
+	}
+
+
+	/**
+	 * Check the signature on a SAML2 message or assertion.
+	 *
+	 * @param SimpleSAML_Configuration $srcMetadata  The metadata of the sender.
+	 * @param SAML2_SignedElement $element  Either a SAML2_Response or a SAML2_Assertion.
+	 */
+	private static function checkSign(SimpleSAML_Configuration $srcMetadata, SAML2_SignedElement $element) {
+
+		$certificates = $element->getCertificates();
+		SimpleSAML_Logger::debug('Found ' . count($certificates) . ' certificates in ' . get_class($element));
+
+		/* Find the certificate that should verify signatures by this entity. */
+		$certArray = SimpleSAML_Utilities::loadPublicKey($srcMetadata->toArray(), FALSE);
+		if ($certArray !== NULL) {
+			if (array_key_exists('PEM', $certArray)) {
+				$pemCert = $certArray['PEM'];
+			} else {
+				/*
+				 * We don't have the full certificate stored. Try to find it
+				 * in the message or the assertion instead.
+				 */
+				if (count($certificates) === 0) {
+					/* We need the full certificate in order to match it against the fingerprint. */
+					SimpleSAML_Logger::debug('No certificate in message when validating against fingerprint.');
+					return FALSE;
+				}
+
+				$certFingerprints = $certArray['certFingerprint'];
+				if (count($certFingerprints) === 0) {
+					/* For some reason, we have a certFingerprint entry without any fingerprints. */
+					throw new SimpleSAML_Error_Exception('certFingerprint array was empty.');
+				}
+
+				$pemCert = self::findCertificate($certFingerprints, $certificates);
+			}
+		} else {
+			/* Attempt CA validation. */
+			$caFile = $srcMetadata->getString('caFile', NULL);
+			if ($caFile === NULL) {
+				throw new SimpleSAML_Error_Exception(
+					'Missing certificate in metadata for ' .
+					var_export($srcMetadata->getString('entityid'), TRUE));
+			}
+			$globalConfig = SimpleSAML_Configuration::getInstance();
+			$caFile = $globalConfig->getPathValue('certdir') . $caFile;
+
+			if (count($certificates) === 0) {
+				/* We need the full certificate in order to check it against the CA file. */
+				SimpleSAML_Logger::debug('No certificate in message when validating with CA.');
+				return FALSE;
+			}
+
+			/* We assume that it is the first certificate that was used to sign the message. */
+			$pemCert = "-----BEGIN CERTIFICATE-----\n" .
+				chunk_split($certificates[0], 64) .
+				"-----END CERTIFICATE-----\n";
+
+			SimpleSAML_Utilities::validateCA($pemCert, $caFile);
+		}
+
+
+		/* Extract the public key from the certificate for validation. */
+		$key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'public'));
+		$key->loadKey($pemCert);
+
+		/*
+		 * Make sure that we have a valid signature on either the response
+		 * or the assertion.
+		 */
+		return $element->validate($key);
+	}
+
+
+	/**
+	 * Decrypt an assertion.
+	 *
+	 * This function takes in a SAML2_Assertion and decrypts it if it is encrypted.
+	 * If it is unencrypted, and encryption is enabled in the metadata, an exception
+	 * will be throws.
+	 *
+	 * @param SimpleSAML_Configuration $srcMetadata  The metadata of the sender (IdP).
+	 * @param SimpleSAML_Configuration $dstMetadata  The metadata of the recipient (SP).
+	 * @param SAML2_Assertion|SAML2_EncryptedAssertion $assertion  The assertion we are decrypting.
+	 * @return SAML2_Assertion  The assertion.
+	 */
+	private static function decryptAssertion(SimpleSAML_Configuration $srcMetadata,
+		SimpleSAML_Configuration $dstMetadata, $assertion) {
+		assert('$assertion instanceof SAML2_Assertion || $assertion instanceof SAML2_EncryptedAssertion');
+
+		if ($assertion instanceof SAML2_Assertion) {
+			$encryptAssertion = $srcMetadata->getBoolean('assertion.encryption', NULL);
+			if ($encryptAssertion === NULL) {
+				$encryptAssertion = $dstMetadata->getBoolean('assertion.encryption', FALSE);
+			}
+			if ($encryptAssertion) {
+				/* The assertion was unencrypted, but we have encryption enabled. */
+				throw new Exception('Received unencrypted assertion, but encryption was enabled.');
+			}
+
+			return $assertion;
+		}
+
+
+		$sharedKey = $srcMetadata->getString('sharedkey', NULL);
+		if ($sharedKey !== NULL) {
+			$key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
+			$key->loadKey($sharedKey);
+		} else {
+			/* Find the private key we should use to decrypt messages to this SP. */
+			$keyArray = SimpleSAML_Utilities::loadPrivateKey($dstMetadata->toArray(), TRUE);
+			if (!array_key_exists('PEM', $keyArray)) {
+				throw new Exception('Unable to locate key we should use to decrypt the assertion.');
+			}
+
+			/* Extract the public key from the certificate for encryption. */
+			$key = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'private'));
+			if (array_key_exists('password', $keyArray)) {
+				$key->passphrase = $keyArray['password'];
+			}
+			$key->loadKey($keyArray['PEM']);
+		}
+
+		return $assertion->getAssertion($key);
+	}
+
+
+	/**
+	 * Retrieve the status code of a response as a sspmod_saml2_error.
+	 *
+	 * @param SAML2_StatusResponse $response  The response.
+	 * @return sspmod_saml2_Error  The error.
+	 */
+	public static function getResponseError(SAML2_StatusResponse $response) {
+
+		$status = $response->getStatus();
+		new sspmod_saml2_Error($status['Code'], $status['SubCode'], $status['Message']);
+	}
+
+
 	/**
 	 * Build an authentication request based on information in the metadata.
 	 *
@@ -119,6 +292,107 @@ class sspmod_saml2_Message {
 		return $lr;
 	}
 
+
+	/**
+	 * Process a response message.
+	 *
+	 * If the response is an error response, we will throw a sspmod_saml2_Error
+	 * exception with the error.
+	 *
+	 * @param SimpleSAML_Configuration $spMetadata  The metadata of the service provider.
+	 * @param SimpleSAML_Configuration $idpMetadata  The metadata of the identity provider.
+	 * @param SAML2_Response $response  The response.
+	 * @return SAML2_Assertion  The assertion in the response, if it is valid.
+	 */
+	public static function processResponse(
+		SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata,
+		SAML2_Response $response
+		) {
+
+		if (!$response->isSuccess()) {
+			throw self::getResponseError($response);
+		}
+
+		/*
+		 * When we get this far, the response itself is valid.
+		 * We only need to check signatures and conditions of the response.
+		 */
+
+		$assertion = $response->getAssertions();
+		if (empty($assertion)) {
+			throw new SimpleSAML_Error_Exception('No assertions found in response from IdP.');
+		} elseif (count($assertion) > 1) {
+			throw new SimpleSAML_Error_Exception('More than one assertion found in response from IdP.');
+		}
+		$assertion = $assertion[0];
+
+		$assertion = self::decryptAssertion($idpMetadata, $spMetadata, $assertion);
+
+		if (!self::checkSign($idpMetadata, $assertion)) {
+			if (!self::checkSign($idpMetadata, $response)) {
+				throw new SimpleSAML_Error_Exception('Neither the assertion nor the response was signed.');
+			}
+		}
+		/* At least one valid signature found. */
+
+
+		/* Make sure that some fields in the assertion matches the same fields in the message. */
+
+		$asrtInResponseTo = $assertion->getInResponseTo();
+		$msgInResponseTo = $response->getInResponseTo();
+		if ($asrtInResponseTo !== NULL && $msgInResponseTo !== NULL) {
+			if ($asrtInResponseTo !== $msgInResponseTo) {
+				throw new SimpleSAML_Error_Exception('InResponseTo in assertion did not match InResponseTo in message.');
+			}
+		}
+
+		$asrtDestination = $assertion->getDestination();
+		$msgDestination = $response->getDestination();
+		if ($asrtDestination !== NULL && $msgDestination !== NULL) {
+			if ($asrtDestination !== $msgDestination) {
+				throw new SimpleSAML_Error_Exception('Destination in assertion did not match Destination in message.');
+			}
+		}
+
+
+		/* Check various properties of the assertion. */
+
+		$notBefore = $assertion->getNotBefore();
+		if ($notBefore > time() + 60) {
+			throw new SimpleSAML_Error_Exception('Received an assertion that is valid in the future. Check clock synchronization on IdP and SP.');
+		}
+
+		$notOnOrAfter = $assertion->getNotOnOrAfter();
+		if ($notOnOrAfter <= time() - 60) {
+			throw new SimpleSAML_Error_Exception('Received an assertion that has expired. Check clock synchronization on IdP and SP.');
+		}
+
+		$sessionNotOnOrAfter = $assertion->getSessionNotOnOrAfter();
+		if ($sessionNotOnOrAfter !== NULL && $sessionNotOnOrAfter <= time() - 60) {
+			throw new SimpleSAML_Error_Exception('Received an assertion with a session that has expired. Check clock synchronization on IdP and SP.');
+		}
+
+		$destination = $assertion->getDestination();
+		$currentURL = SimpleSAML_Utilities::selfURLNoQuery();
+		if ($destination !== $currentURL) {
+			throw new Exception('Recipient in assertion doesn\'t match the current URL. Recipient is "' .
+				$destination . '", current URL is "' . $currentURL . '".');
+		}
+
+		$validAudiences = $assertion->getValidAudiences();
+		if ($validAudiences !== NULL) {
+			$spEntityId = $spMetadata->getString('entityid');
+			if (!in_array($spEntityId, $validAudiences, TRUE)) {
+				$candidates = '[' . implode('], [', $validAudiences) . ']';
+				throw new SimpleSAML_Error_Exception('This SP [' . $spEntityId . ']  is not a valid audience for the assertion. Candidates were: ' . $candidates);
+			}
+		}
+
+		/* As far as we can tell, the assertion is valid. */
+		return $assertion;
+	}
+
+
 }
 
 ?>
\ No newline at end of file
diff --git a/modules/saml2/www/sp/acs.php b/modules/saml2/www/sp/acs.php
index a43c70dbb686ae1e600396b39163d6603001ed6d..0230131f8538d33365e11a64a8fafa30e2aae427 100644
--- a/modules/saml2/www/sp/acs.php
+++ b/modules/saml2/www/sp/acs.php
@@ -4,15 +4,18 @@
  * Assertion consumer service handler for SAML 2.0 SP authentication client.
  */
 
-if (!array_key_exists('SAMLResponse', $_POST)) {
-	throw new SimpleSAML_Error_BadRequest('Missing SAMLResponse to AssertionConsumerService');
+$b = SAML2_Binding::getCurrentBinding();
+$response = $b->receive();
+if (!($response instanceof SAML2_Response)) {
+	throw new SimpleSAML_Error_BadRequest('Invalid message received to AssertionConsumerService endpoint.');
 }
 
-if (!array_key_exists('RelayState', $_POST)) {
-	throw new SimpleSAML_Error_BadRequest('Missing RelayState to AssertionConsumerService');
+$relayState = $response->getRelayState();
+if (empty($relayState)) {
+	throw new SimpleSAML_Error_BadRequest('Missing relaystate in message received on AssertionConsumerService endpoint.');
 }
 
-$state = SimpleSAML_Auth_State::loadState($_POST['RelayState'], sspmod_saml2_Auth_Source_SP::STAGE_SENT);
+$state = SimpleSAML_Auth_State::loadState($relayState, sspmod_saml2_Auth_Source_SP::STAGE_SENT);
 
 /* Find authentication source. */
 assert('array_key_exists(sspmod_saml2_Auth_Source_SP::AUTHID, $state)');
@@ -23,22 +26,14 @@ if ($source === NULL) {
 	throw new Exception('Could not find authentication source with id ' . $sourceId);
 }
 
-$config = SimpleSAML_Configuration::getInstance();
-$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
-
-$binding = new SimpleSAML_Bindings_SAML20_HTTPPost($config, $metadata);
-$authnResponse = $binding->decodeResponse($_POST);
-
-$result = $authnResponse->process();
-
-/* Check status code. */
-if($result === FALSE) {
-	/* Not successful. */
-	SimpleSAML_Auth_State::throwException($state, $authnResponse->getStatus()->toException());
+$idp = $response->getIssuer();
+if ($idp === NULL) {
+	throw new Exception('Missing <saml:Issuer> in message delivered to AssertionConsumerService.');
 }
 
-/* The response should include the entity id of the IdP. */
-$idp = $authnResponse->getIssuer();
+$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+$idpMetadata = $metadata->getMetaDataConfig($idp, 'saml20-idp-remote');
+$spMetadata = $metadata->getMetaDataConfig($source->getEntityId(), 'saml20-sp-hosted');
 
 /* Check if the IdP is allowed to authenticate users for this authentication source. */
 if (!$source->isIdPValid($idp)) {
@@ -46,25 +41,29 @@ if (!$source->isIdPValid($idp)) {
 		'. The IdP was ' . var_export($idp, TRUE));
 }
 
-/*
- * Retrieve the name identifier. We also convert it to the format used by the
- * logout request handler.
- */
-$nameId = $authnResponse->getNameID();
-$nameId['Value'] = $nameId['value'];
-unset($nameId['value']);
+
+try {
+	$assertion = sspmod_saml2_Message::processResponse($spMetadata, $idpMetadata, $response);
+} catch (sspmod_saml2_Error $e) {
+	/* The status of the response wasn't "success". */
+	$e = $e->toException();
+	SimpleSAML_Auth_State::throwException($state, $e);
+}
+
+$nameId = $assertion->getNameID();
+$sessionIndex = $assertion->getSessionIndex();
 
 /* We need to save the NameID and SessionIndex for logout. */
 $logoutState = array(
 	sspmod_saml2_Auth_Source_SP::LOGOUT_IDP => $idp,
 	sspmod_saml2_Auth_Source_SP::LOGOUT_NAMEID => $nameId,
-	sspmod_saml2_Auth_Source_SP::LOGOUT_SESSIONINDEX => $authnResponse->getSessionIndex(),
+	sspmod_saml2_Auth_Source_SP::LOGOUT_SESSIONINDEX => $sessionIndex,
 	);
 $state['LogoutState'] = $logoutState;
 
 $source->onLogin($idp, $state);
 
-$state['Attributes'] = $authnResponse->getAttributes();
+$state['Attributes'] = $assertion->getAttributes();
 SimpleSAML_Auth_Source::completeAuth($state);
 
 ?>
\ No newline at end of file