Get public key from metadata.
public getPublicKeys ( string | null $use = null, boolean $required = false, string $prefix = '' ) : array | null | ||
$use | string | null | The purpose this key can be used for. (encryption or signing). |
$required | boolean | Whether the public key is required. If this is true, a missing key will cause an exception. Default is false. |
$prefix | string | The prefix which should be used when reading from the metadata array. Defaults to ''. |
Résultat | array | null | Public key data, or null if no public key or was found. |
/** * Add a certificate. * * Helper function for adding a certificate to the metadata. * * @param \SAML2\XML\md\RoleDescriptor $rd The RoleDescriptor the certificate should be added to. * @param SimpleSAML_Configuration $metadata The metadata of the entity. */ private function addCertificate(\SAML2\XML\md\RoleDescriptor $rd, SimpleSAML_Configuration $metadata) { $keys = $metadata->getPublicKeys(); if ($keys !== null) { foreach ($keys as $key) { if ($key['type'] !== 'X509Certificate') { continue; } if (!isset($key['signing']) || $key['signing'] === true) { $this->addX509KeyDescriptor($rd, 'signing', $key['X509Certificate']); } if (!isset($key['encryption']) || $key['encryption'] === true) { $this->addX509KeyDescriptor($rd, 'encryption', $key['X509Certificate']); } } } if ($metadata->hasValue('https.certData')) { $this->addX509KeyDescriptor($rd, 'signing', $metadata->getString('https.certData')); } }
/** * Get public key or certificate from metadata. * * This function implements a function to retrieve the public key or certificate from * a metadata array. * * It will search for the following elements in the metadata: * 'certData' The certificate as a base64-encoded string. * 'certificate' A file with a certificate or public key in PEM-format. * 'certFingerprint' The fingerprint of the certificate. Can be a single fingerprint, * or an array of multiple valid fingerprints. * * This function will return an array with these elements: * 'PEM' The public key/certificate in PEM-encoding. * 'certData' The certificate data, base64 encoded, on a single line. (Only * present if this is a certificate.) * 'certFingerprint' Array of valid certificate fingerprints. (Only present * if this is a certificate.) * * @param SimpleSAML_Configuration $metadata The metadata. * @param bool $required Whether the private key is required. If this is TRUE, a * missing key will cause an exception. Default is FALSE. * @param string $prefix The prefix which should be used when reading from the metadata * array. Defaults to ''. * @return array|NULL Public key or certificate data, or NULL if no public key or * certificate was found. */ public static function loadPublicKey(SimpleSAML_Configuration $metadata, $required = FALSE, $prefix = '') { assert('is_bool($required)'); assert('is_string($prefix)'); $keys = $metadata->getPublicKeys(NULL, FALSE, $prefix); if ($keys !== NULL) { foreach ($keys as $key) { if ($key['type'] !== 'X509Certificate') { continue; } if ($key['signing'] !== TRUE) { continue; } $certData = $key['X509Certificate']; $pem = "-----BEGIN CERTIFICATE-----\n" . chunk_split($certData, 64) . "-----END CERTIFICATE-----\n"; $certFingerprint = strtolower(sha1(base64_decode($certData))); return array('certData' => $certData, 'PEM' => $pem, 'certFingerprint' => array($certFingerprint)); } /* No valid key found. */ } elseif ($metadata->hasValue($prefix . 'certFingerprint')) { /* We only have a fingerprint available. */ $fps = $metadata->getArrayizeString($prefix . 'certFingerprint'); /* Normalize fingerprint(s) - lowercase and no colons. */ foreach ($fps as &$fp) { assert('is_string($fp)'); $fp = strtolower(str_replace(':', '', $fp)); } /* We can't build a full certificate from a fingerprint, and may as well * return an array with only the fingerprint(s) immediately. */ return array('certFingerprint' => $fps); } /* No public key/certificate available. */ if ($required) { throw new Exception('No public key / certificate found in metadata.'); } else { return NULL; } }
/** * This function receives a SAML 1.1 artifact. * * @param SimpleSAML_Configuration $spMetadata The metadata of the SP. * @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP. * @return string The <saml1p:Response> element, as an XML string. */ public static function receive(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata) { $artifacts = self::getArtifacts(); $request = self::buildRequest($artifacts); \SimpleSAML\Utils\XML::debugSAMLMessage($request, 'out'); $url = $idpMetadata->getDefaultEndpoint('ArtifactResolutionService', array('urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding')); $url = $url['Location']; $peerPublicKeys = $idpMetadata->getPublicKeys('signing', TRUE); $certData = ''; foreach ($peerPublicKeys as $key) { if ($key['type'] !== 'X509Certificate') { continue; } $certData .= "-----BEGIN CERTIFICATE-----\n" . chunk_split($key['X509Certificate'], 64) . "-----END CERTIFICATE-----\n"; } $file = SimpleSAML\Utils\System::getTempDir() . DIRECTORY_SEPARATOR . sha1($certData) . '.crt'; if (!file_exists($file)) { SimpleSAML\Utils\System::writeFile($file, $certData); } $spKeyCertFile = \SimpleSAML\Utils\Config::getCertPath($spMetadata->getString('privatekey')); $opts = array('ssl' => array('verify_peer' => TRUE, 'cafile' => $file, 'local_cert' => $spKeyCertFile, 'capture_peer_cert' => TRUE, 'capture_peer_chain' => TRUE), 'http' => array('method' => 'POST', 'content' => $request, 'header' => 'SOAPAction: http://www.oasis-open.org/committees/security' . "\r\n" . 'Content-Type: text/xml')); // Fetch the artifact $response = \SimpleSAML\Utils\HTTP::fetch($url, $opts); if ($response === FALSE) { throw new SimpleSAML_Error_Exception('Failed to retrieve assertion from IdP.'); } \SimpleSAML\Utils\XML::debugSAMLMessage($response, 'in'); // Find the response in the SOAP message $response = self::extractResponse($response); return $response; }
/** * Encrypt an assertion. * * This function takes in a SAML2_Assertion and encrypts it if encryption of * assertions are enabled in the metadata. * * @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP. * @param SimpleSAML_Configuration $spMetadata The metadata of the SP. * @param SAML2_Assertion $assertion The assertion we are encrypting. * @return SAML2_Assertion|SAML2_EncryptedAssertion The assertion. */ private static function encryptAssertion(SimpleSAML_Configuration $idpMetadata, SimpleSAML_Configuration $spMetadata, SAML2_Assertion $assertion) { $encryptAssertion = $spMetadata->getBoolean('assertion.encryption', NULL); if ($encryptAssertion === NULL) { $encryptAssertion = $idpMetadata->getBoolean('assertion.encryption', FALSE); } if (!$encryptAssertion) { /* We are _not_ encrypting this assertion, and are therefore done. */ return $assertion; } $sharedKey = $spMetadata->getString('sharedkey', NULL); if ($sharedKey !== NULL) { $key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC); $key->loadKey($sharedKey); } else { $keys = $spMetadata->getPublicKeys('encryption', TRUE); $key = $keys[0]; switch ($key['type']) { case 'X509Certificate': $pemKey = "-----BEGIN CERTIFICATE-----\n" . chunk_split($key['X509Certificate'], 64) . "-----END CERTIFICATE-----\n"; break; default: throw new SimpleSAML_Error_Exception('Unsupported encryption key type: ' . $key['type']); } /* Extract the public key from the certificate for encryption. */ $key = new XMLSecurityKey(XMLSecurityKey::RSA_OAEP_MGF1P, array('type' => 'public')); $key->loadKey($pemKey); } $ea = new SAML2_EncryptedAssertion(); $ea->setAssertion($assertion, $key); return $ea; }
/** * Retrieve the encryption key for the given entity. * * @param SimpleSAML_Configuration $metadata The metadata of the entity. * @return XMLSecurityKey The encryption key. */ public static function getEncryptionKey(SimpleSAML_Configuration $metadata) { $sharedKey = $metadata->getString('sharedkey', NULL); if ($sharedKey !== NULL) { $key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC); $key->loadKey($sharedKey); return $key; } $keys = $metadata->getPublicKeys('encryption', TRUE); foreach ($keys as $key) { switch ($key['type']) { case 'X509Certificate': $pemKey = "-----BEGIN CERTIFICATE-----\n" . chunk_split($key['X509Certificate'], 64) . "-----END CERTIFICATE-----\n"; $key = new XMLSecurityKey(XMLSecurityKey::RSA_OAEP_MGF1P, array('type' => 'public')); $key->loadKey($pemKey); return $key; } } throw new SimpleSAML_Error_Exception('No supported encryption key in ' . var_export($metadata->getString('entityid'), TRUE)); }
/** * This function sends the SOAP message to the service location and returns SOAP response * * @param SAML2_Message $m The request that should be sent. * @param SimpleSAML_Configuration $srcMetadata The metadata of the issuer of the message. * @param SimpleSAML_Configuration $dstMetadata The metadata of the destination of the message. * @return SAML2_Message The response we received. */ public function send(SAML2_Message $msg, SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata = NULL) { $issuer = $msg->getIssuer(); $ctxOpts = array('ssl' => array('capture_peer_cert' => TRUE)); // Determine if we are going to do a MutualSSL connection between the IdP and SP - Shoaib if ($srcMetadata->hasValue('saml.SOAPClient.certificate')) { $ctxOpts['ssl']['local_cert'] = SimpleSAML_Utilities::resolveCert($srcMetadata->getString('saml.SOAPClient.certificate')); if ($srcMetadata->hasValue('saml.SOAPClient.privatekey_pass')) { $ctxOpts['ssl']['passphrase'] = $srcMetadata->getString('saml.SOAPClient.privatekey_pass'); } } else { /* Use the SP certificate and privatekey if it is configured. */ $privateKey = SimpleSAML_Utilities::loadPrivateKey($srcMetadata); $publicKey = SimpleSAML_Utilities::loadPublicKey($srcMetadata); if ($privateKey !== NULL && $publicKey !== NULL && isset($publicKey['PEM'])) { $keyCertData = $privateKey['PEM'] . $publicKey['PEM']; $file = SimpleSAML_Utilities::getTempDir() . '/' . sha1($keyCertData) . '.pem'; if (!file_exists($file)) { SimpleSAML_Utilities::writeFile($file, $keyCertData); } $ctxOpts['ssl']['local_cert'] = $file; if (isset($privateKey['password'])) { $ctxOpts['ssl']['passphrase'] = $privateKey['password']; } } } // do peer certificate verification if ($dstMetadata !== NULL) { $peerPublicKeys = $dstMetadata->getPublicKeys('signing', TRUE); $certData = ''; foreach ($peerPublicKeys as $key) { if ($key['type'] !== 'X509Certificate') { continue; } $certData .= "-----BEGIN CERTIFICATE-----\n" . chunk_split($key['X509Certificate'], 64) . "-----END CERTIFICATE-----\n"; } $peerCertFile = SimpleSAML_Utilities::getTempDir() . '/' . sha1($certData) . '.pem'; if (!file_exists($peerCertFile)) { SimpleSAML_Utilities::writeFile($peerCertFile, $certData); } // create ssl context $ctxOpts['ssl']['verify_peer'] = TRUE; $ctxOpts['ssl']['verify_depth'] = 1; $ctxOpts['ssl']['cafile'] = $peerCertFile; } $context = stream_context_create($ctxOpts); if ($context === NULL) { throw new Exception('Unable to create SSL stream context'); } $options = array('uri' => $issuer, 'location' => $msg->getDestination(), 'stream_context' => $context); $x = new SoapClient(NULL, $options); // Add soap-envelopes $request = $msg->toSignedXML(); $request = self::START_SOAP_ENVELOPE . $request->ownerDocument->saveXML($request) . self::END_SOAP_ENVELOPE; SimpleSAML_Utilities::debugMessage($request, 'out'); $action = 'http://www.oasis-open.org/committees/security'; $version = '1.1'; $destination = $msg->getDestination(); /* Perform SOAP Request over HTTP */ $soapresponsexml = $x->__doRequest($request, $destination, $action, $version); if ($soapresponsexml === NULL || $soapresponsexml === "") { throw new Exception('Empty SOAP response, check peer certificate.'); } SimpleSAML_Utilities::debugMessage($soapresponsexml, 'in'); // Convert to SAML2_Message (DOMElement) $dom = new DOMDocument(); if (!$dom->loadXML($soapresponsexml)) { throw new Exception('Not a SOAP response.'); } $soapfault = $this->getSOAPFault($dom); if (isset($soapfault)) { throw new Exception($soapfault); } //Extract the message from the response $xml = $dom->firstChild; /* Soap Envelope */ $samlresponse = SAML2_Utils::xpQuery($dom->firstChild, '/soap-env:Envelope/soap-env:Body/*[1]'); $samlresponse = SAML2_Message::fromXML($samlresponse[0]); /* Add validator to message which uses the SSL context. */ self::addSSLValidator($samlresponse, $context); SimpleSAML_Logger::debug("Valid ArtifactResponse received from IdP"); return $samlresponse; }
/** * Get public key or certificate from metadata. * * This function implements a function to retrieve the public key or certificate from a metadata array. * * It will search for the following elements in the metadata: * - 'certData': The certificate as a base64-encoded string. * - 'certificate': A file with a certificate or public key in PEM-format. * - 'certFingerprint': The fingerprint of the certificate. Can be a single fingerprint, or an array of multiple * valid fingerprints. * * This function will return an array with these elements: * - 'PEM': The public key/certificate in PEM-encoding. * - 'certData': The certificate data, base64 encoded, on a single line. (Only present if this is a certificate.) * - 'certFingerprint': Array of valid certificate fingerprints. (Only present if this is a certificate.) * * @param \SimpleSAML_Configuration $metadata The metadata. * @param bool $required Whether the private key is required. If this is TRUE, a missing key * will cause an exception. Default is FALSE. * @param string $prefix The prefix which should be used when reading from the metadata array. * Defaults to ''. * * @return array|NULL Public key or certificate data, or NULL if no public key or certificate was found. * @throws \InvalidArgumentException If $metadata is not an instance of \SimpleSAML_Configuration, $required is not * boolean or $prefix is not a string. * @throws \SimpleSAML_Error_Exception If no private key is found in the metadata, or it was not possible to load * it. * * @author Andreas Solberg, UNINETT AS <*****@*****.**> * @author Olav Morken, UNINETT AS <*****@*****.**> * @author Lasse Birnbaum Jensen */ public static function loadPublicKey(\SimpleSAML_Configuration $metadata, $required = false, $prefix = '') { if (!is_bool($required) || !is_string($prefix)) { throw new \InvalidArgumentException('Invalid input parameters.'); } $keys = $metadata->getPublicKeys(null, false, $prefix); if ($keys !== null) { foreach ($keys as $key) { if ($key['type'] !== 'X509Certificate') { continue; } if ($key['signing'] !== true) { continue; } $certData = $key['X509Certificate']; $pem = "-----BEGIN CERTIFICATE-----\n" . chunk_split($certData, 64) . "-----END CERTIFICATE-----\n"; $certFingerprint = strtolower(sha1(base64_decode($certData))); return array('certData' => $certData, 'PEM' => $pem, 'certFingerprint' => array($certFingerprint)); } // no valid key found } elseif ($metadata->hasValue($prefix . 'certFingerprint')) { // we only have a fingerprint available $fps = $metadata->getArrayizeString($prefix . 'certFingerprint'); // normalize fingerprint(s) - lowercase and no colons foreach ($fps as &$fp) { assert('is_string($fp)'); $fp = strtolower(str_replace(':', '', $fp)); } // We can't build a full certificate from a fingerprint, and may as well return an array with only the //fingerprint(s) immediately. return array('certFingerprint' => $fps); } // no public key/certificate available if ($required) { throw new \SimpleSAML_Error_Exception('No public key / certificate found in metadata.'); } else { return null; } }
/** * This function sends the SOAP message to the service location and returns SOAP response * * @param SAML2_Message $msg The request that should be sent. * @param SimpleSAML_Configuration $srcMetadata The metadata of the issuer of the message. * @param SimpleSAML_Configuration $dstMetadata The metadata of the destination of the message. * @return SAML2_Message The response we received. * @throws Exception */ public function send(SAML2_Message $msg, SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata = NULL) { $issuer = $msg->getIssuer(); $ctxOpts = array('ssl' => array('capture_peer_cert' => TRUE)); /* Determine if we are going to do a MutualSSL connection between the IdP and SP - Shoaib */ if ($srcMetadata->hasValue('saml.SOAPClient.certificate')) { $cert = $srcMetadata->getValue('saml.SOAPClient.certificate'); if ($cert !== FALSE) { $ctxOpts['ssl']['local_cert'] = SimpleSAML_Utilities::resolveCert($srcMetadata->getString('saml.SOAPClient.certificate')); if ($srcMetadata->hasValue('saml.SOAPClient.privatekey_pass')) { $ctxOpts['ssl']['passphrase'] = $srcMetadata->getString('saml.SOAPClient.privatekey_pass'); } } } else { /* Use the SP certificate and privatekey if it is configured. */ $privateKey = SimpleSAML_Utilities::loadPrivateKey($srcMetadata); $publicKey = SimpleSAML_Utilities::loadPublicKey($srcMetadata); if ($privateKey !== NULL && $publicKey !== NULL && isset($publicKey['PEM'])) { $keyCertData = $privateKey['PEM'] . $publicKey['PEM']; $file = SimpleSAML_Utilities::getTempDir() . '/' . sha1($keyCertData) . '.pem'; if (!file_exists($file)) { SimpleSAML_Utilities::writeFile($file, $keyCertData); } $ctxOpts['ssl']['local_cert'] = $file; if (isset($privateKey['password'])) { $ctxOpts['ssl']['passphrase'] = $privateKey['password']; } } } /* Do peer certificate verification */ if ($dstMetadata !== NULL) { $peerPublicKeys = $dstMetadata->getPublicKeys('signing', TRUE); $certData = ''; foreach ($peerPublicKeys as $key) { if ($key['type'] !== 'X509Certificate') { continue; } $certData .= "-----BEGIN CERTIFICATE-----\n" . chunk_split($key['X509Certificate'], 64) . "-----END CERTIFICATE-----\n"; } $peerCertFile = SimpleSAML_Utilities::getTempDir() . '/' . sha1($certData) . '.pem'; if (!file_exists($peerCertFile)) { SimpleSAML_Utilities::writeFile($peerCertFile, $certData); } /* Create ssl context */ $ctxOpts['ssl']['verify_peer'] = TRUE; $ctxOpts['ssl']['verify_depth'] = 1; $ctxOpts['ssl']['cafile'] = $peerCertFile; } $ctxOpts['http']['header'] = 'SOAPAction: "http://www.oasis-open.org/committees/security"' . "\n"; if ($this->username !== NULL && $this->password !== NULL) { /* Add HTTP Basic authentication header. */ $authData = $this->username . ':' . $this->password; $authData = base64_encode($authData); $ctxOpts['http']['header'] .= 'Authorization: Basic ' . $authData . "\n"; } if ($srcMetadata->hasValue('saml.SOAPClient.proxyhost')) { $options['proxy_host'] = $srcMetadata->getValue('saml.SOAPClient.proxyhost'); } if ($srcMetadata->hasValue('saml.SOAPClient.proxyport')) { $options['proxy_port'] = $srcMetadata->getValue('saml.SOAPClient.proxyport'); } $x = new SoapClient(NULL, $options); /* Add soap-envelopes */ $request = $msg->toSignedXML(); $request = self::START_SOAP_ENVELOPE . $request->ownerDocument->saveXML($request) . self::END_SOAP_ENVELOPE; SAML2_Utils::getContainer()->debugMessage($request, 'out'); $ctxOpts['http']['content'] = $request; $ctxOpts['http']['header'] .= 'Content-Type: text/xml; charset=utf-8' . "\n"; $ctxOpts['http']['method'] = 'POST'; $destination = $msg->getDestination(); /* Perform SOAP Request over HTTP */ $context = stream_context_create($ctxOpts); if ($context === NULL) { throw new Exception('Unable to create stream context'); } $soapresponsexml = @file_get_contents($destination, FALSE, $context); if ($soapresponsexml === FALSE) { throw new Exception('Error processing SOAP call: ' . SimpleSAML_Utilities::getLastError()); } SAML2_Utils::getContainer()->debugMessage($soapresponsexml, 'in'); /* Convert to SAML2_Message (DOMElement) */ try { $dom = SAML2_DOMDocumentFactory::fromString($soapresponsexml); } catch (SAML2_Exception_RuntimeException $e) { throw new Exception('Not a SOAP response.', 0, $e); } $soapfault = $this->getSOAPFault($dom); if (isset($soapfault)) { throw new Exception($soapfault); } /* Extract the message from the response */ $samlresponse = SAML2_Utils::xpQuery($dom->firstChild, '/soap-env:Envelope/soap-env:Body/*[1]'); $samlresponse = SAML2_Message::fromXML($samlresponse[0]); /* Add validator to message which uses the SSL context. */ self::addSSLValidator($samlresponse, $context); SAML2_Utils::getContainer()->getLogger()->debug("Valid ArtifactResponse received from IdP"); return $samlresponse; }
/** * 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. */ public static function checkSign(SimpleSAML_Configuration $srcMetadata, SAML2_SignedElement $element) { /* Find the public key that should verify signatures by this entity. */ $keys = $srcMetadata->getPublicKeys('signing'); if ($keys !== NULL) { $pemKeys = array(); foreach ($keys as $key) { switch ($key['type']) { case 'X509Certificate': $pemKeys[] = "-----BEGIN CERTIFICATE-----\n" . chunk_split($key['X509Certificate'], 64) . "-----END CERTIFICATE-----\n"; break; default: SimpleSAML_Logger::debug('Skipping unknown key type: ' . $key['type']); } } } elseif ($srcMetadata->hasValue('certFingerprint')) { $certFingerprint = $srcMetadata->getArrayizeString('certFingerprint'); foreach ($certFingerprint as &$fp) { $fp = strtolower(str_replace(':', '', $fp)); } $certificates = $element->getCertificates(); /* * 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; } else { SimpleSAML_Logger::debug('Found ' . count($certificates) . ' certificates in ' . get_class($element)); } $pemCert = self::findCertificate($certFingerprint, $certificates); $pemKeys = array($pemCert); } 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)); } $caFile = SimpleSAML_Utilities::resolveCert($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); $pemKeys = array($pemCert); } SimpleSAML_Logger::debug('Has ' . count($pemKeys) . ' candidate keys for validation.'); $lastException = NULL; foreach ($pemKeys as $i => $pem) { $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'public')); $key->loadKey($pem); try { /* * Make sure that we have a valid signature on either the response * or the assertion. */ $res = $element->validate($key); if ($res) { SimpleSAML_Logger::debug('Validation with key #' . $i . ' succeeded.'); return TRUE; } SimpleSAML_Logger::debug('Validation with key #' . $i . ' failed without exception.'); } catch (Exception $e) { SimpleSAML_Logger::debug('Validation with key #' . $i . ' failed with exception: ' . $e->getMessage()); $lastException = $e; } } /* We were unable to validate the signature with any of our keys. */ if ($lastException !== NULL) { throw $lastException; } else { return FALSE; } }