/** * @param SimpleSAML_Configuration $configuration * @param string $prefix * * @return array */ private static function pluckConfiguration(SimpleSAML_Configuration $configuration, $prefix = '') { $extracted = array(); // ported from // https://github.com/simplesamlphp/simplesamlphp/blob/3d735912342767d391297cc5e13272a76730aca0/lib/SimpleSAML/Configuration.php#L1092 if ($configuration->hasValue($prefix . 'keys')) { $extracted['keys'] = $configuration->getArray($prefix . 'keys'); } // ported from // https://github.com/simplesamlphp/simplesamlphp/blob/3d735912342767d391297cc5e13272a76730aca0/lib/SimpleSAML/Configuration.php#L1108 if ($configuration->hasValue($prefix . 'certData')) { $extracted['certificateData'] = $configuration->getString($prefix . 'certData'); } // ported from // https://github.com/simplesamlphp/simplesamlphp/blob/3d735912342767d391297cc5e13272a76730aca0/lib/SimpleSAML/Configuration.php#L1119 if ($configuration->hasValue($prefix . 'certificate')) { $extracted['certificateData'] = $configuration->getString($prefix . 'certificate'); } // ported from // https://github.com/simplesamlphp/simplesamlphp/blob/3d735912342767d391297cc5e13272a76730aca0/modules/saml/lib/Message.php#L161 if ($configuration->hasValue($prefix . 'certFingerprint')) { $extracted['certificateFingerprint'] = $configuration->getArrayizeString('certFingerprint'); } $extracted['assertionEncryptionEnabled'] = $configuration->getBoolean('assertion.encryption', FALSE); if ($configuration->has('sharedKey')) { $extracted['sharedKey'] = $configuration->getString('sharedKey'); } return $extracted; }
/** * Send an authenticationResponse using HTTP-POST. * * @param string $response The response which should be sent. * @param SimpleSAML_Configuration $idpmd The metadata of the IdP which is sending the response. * @param SimpleSAML_Configuration $spmd The metadata of the SP which is receiving the response. * @param string|null $relayState The relaystate for the SP. * @param string $shire The shire which should receive the response. */ public function sendResponse($response, SimpleSAML_Configuration $idpmd, SimpleSAML_Configuration $spmd, $relayState, $shire) { \SimpleSAML\Utils\XML::checkSAMLMessage($response, 'saml11'); $privatekey = SimpleSAML\Utils\Crypto::loadPrivateKey($idpmd, true); $publickey = SimpleSAML\Utils\Crypto::loadPublicKey($idpmd, true); $responsedom = new DOMDocument(); $responsedom->loadXML(str_replace("\r", "", $response)); $responseroot = $responsedom->getElementsByTagName('Response')->item(0); $firstassertionroot = $responsedom->getElementsByTagName('Assertion')->item(0); /* Determine what we should sign - either the Response element or the Assertion. The default is to sign the * Assertion, but that can be overridden by the 'signresponse' option in the SP metadata or * 'saml20.signresponse' in the global configuration. * * TODO: neither 'signresponse' nor 'shib13.signresponse' are valid options any longer. Remove! */ if ($spmd->hasValue('signresponse')) { $signResponse = $spmd->getBoolean('signresponse'); } else { $signResponse = $this->configuration->getBoolean('shib13.signresponse', true); } // check if we have an assertion to sign. Force to sign the response if not if ($firstassertionroot === null) { $signResponse = true; } $signer = new SimpleSAML_XML_Signer(array('privatekey_array' => $privatekey, 'publickey_array' => $publickey, 'id' => $signResponse ? 'ResponseID' : 'AssertionID')); if ($idpmd->hasValue('certificatechain')) { $signer->addCertificate($idpmd->getString('certificatechain')); } if ($signResponse) { // sign the response - this must be done after encrypting the assertion // we insert the signature before the saml2p:Status element $statusElements = SimpleSAML\Utils\XML::getDOMChildren($responseroot, 'Status', '@saml1p'); assert('count($statusElements) === 1'); $signer->sign($responseroot, $responseroot, $statusElements[0]); } else { /* Sign the assertion */ $signer->sign($firstassertionroot, $firstassertionroot); } $response = $responsedom->saveXML(); \SimpleSAML\Utils\XML::debugSAMLMessage($response, 'out'); \SimpleSAML\Utils\HTTP::submitPOSTData($shire, array('TARGET' => $relayState, 'SAMLResponse' => base64_encode($response))); }
/** * Send an authenticationResponse using HTTP-POST. * * @param string $response The response which should be sent. * @param SimpleSAML_Configuration $idpmd The metadata of the IdP which is sending the response. * @param SimpleSAML_Configuration $spmd The metadata of the SP which is receiving the response. * @param string|NULL $relayState The relaystate for the SP. * @param string $shire The shire which should receive the response. */ public function sendResponse($response, SimpleSAML_Configuration $idpmd, SimpleSAML_Configuration $spmd, $relayState, $shire) { SimpleSAML_Utilities::validateXMLDocument($response, 'saml11'); $privatekey = SimpleSAML_Utilities::loadPrivateKey($idpmd, TRUE); $publickey = SimpleSAML_Utilities::loadPublicKey($idpmd, TRUE); $responsedom = new DOMDocument(); $responsedom->loadXML(str_replace("\r", "", $response)); $responseroot = $responsedom->getElementsByTagName('Response')->item(0); $firstassertionroot = $responsedom->getElementsByTagName('Assertion')->item(0); /* Determine what we should sign - either the Response element or the Assertion. The default * is to sign the Assertion, but that can be overridden by the 'signresponse' option in the * SP metadata or 'saml20.signresponse' in the global configuration. */ $signResponse = FALSE; if ($spmd->hasValue('signresponse')) { $signResponse = $spmd->getBoolean['signresponse']; } else { $signResponse = $this->configuration->getBoolean('shib13.signresponse', TRUE); } /* Check if we have an assertion to sign. Force to sign the response if not. */ if ($firstassertionroot === NULL) { $signResponse = TRUE; } $signer = new SimpleSAML_XML_Signer(array('privatekey_array' => $privatekey, 'publickey_array' => $publickey, 'id' => $signResponse ? 'ResponseID' : 'AssertionID')); if ($idpmd->hasValue('certificatechain')) { $signer->addCertificate($idpmd->getString('certificatechain')); } if ($signResponse) { /* Sign the response - this must be done after encrypting the assertion. */ /* We insert the signature before the saml2p:Status element. */ $statusElements = SimpleSAML_Utilities::getDOMChildren($responseroot, 'Status', '@saml1p'); assert('count($statusElements) === 1'); $signer->sign($responseroot, $responseroot, $statusElements[0]); } else { /* Sign the assertion */ $signer->sign($firstassertionroot, $firstassertionroot); } $response = $responsedom->saveXML(); SimpleSAML_Utilities::debugMessage($response, 'out'); SimpleSAML_Utilities::postRedirect($shire, array('TARGET' => $relayState, 'SAMLResponse' => base64_encode($response))); }
/** * Build a authentication response. * * @param array $idp Metadata for the IdP the response is sent from. * @param array $sp Metadata for the SP the response is sent to. * @param string $shire The endpoint on the SP the response is sent to. * @param array|NULL $attributes The attributes which should be included in the response. * @return string The response. */ public function generate(SimpleSAML_Configuration $idp, SimpleSAML_Configuration $sp, $shire, $attributes) { assert('is_string($shire)'); assert('$attributes === NULL || is_array($attributes)'); if ($sp->hasValue('scopedattributes')) { $scopedAttributes = $sp->getArray('scopedattributes'); } elseif ($idp->hasValue('scopedattributes')) { $scopedAttributes = $idp->getArray('scopedattributes'); } else { $scopedAttributes = array(); } $id = SimpleSAML\Utils\Random::generateID(); $issueInstant = SimpleSAML\Utils\Time::generateTimestamp(); // 30 seconds timeskew back in time to allow differing clocks. $notBefore = SimpleSAML\Utils\Time::generateTimestamp(time() - 30); $assertionExpire = SimpleSAML\Utils\Time::generateTimestamp(time() + 60 * 5); # 5 minutes $assertionid = SimpleSAML\Utils\Random::generateID(); $spEntityId = $sp->getString('entityid'); $audience = $sp->getString('audience', $spEntityId); $base64 = $sp->getBoolean('base64attributes', FALSE); $namequalifier = $sp->getString('NameQualifier', $spEntityId); $nameid = SimpleSAML\Utils\Random::generateID(); $subjectNode = '<Subject>' . '<NameIdentifier' . ' Format="urn:mace:shibboleth:1.0:nameIdentifier"' . ' NameQualifier="' . htmlspecialchars($namequalifier) . '"' . '>' . htmlspecialchars($nameid) . '</NameIdentifier>' . '<SubjectConfirmation>' . '<ConfirmationMethod>' . 'urn:oasis:names:tc:SAML:1.0:cm:bearer' . '</ConfirmationMethod>' . '</SubjectConfirmation>' . '</Subject>'; $encodedattributes = ''; if (is_array($attributes)) { $encodedattributes .= '<AttributeStatement>'; $encodedattributes .= $subjectNode; foreach ($attributes as $name => $value) { $encodedattributes .= $this->enc_attribute($name, $value, $base64, $scopedAttributes); } $encodedattributes .= '</AttributeStatement>'; } /* * The SAML 1.1 response message */ $response = '<Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" IssueInstant="' . $issueInstant . '" MajorVersion="1" MinorVersion="1" Recipient="' . htmlspecialchars($shire) . '" ResponseID="' . $id . '"> <Status> <StatusCode Value="samlp:Success" /> </Status> <Assertion xmlns="urn:oasis:names:tc:SAML:1.0:assertion" AssertionID="' . $assertionid . '" IssueInstant="' . $issueInstant . '" Issuer="' . htmlspecialchars($idp->getString('entityid')) . '" MajorVersion="1" MinorVersion="1"> <Conditions NotBefore="' . $notBefore . '" NotOnOrAfter="' . $assertionExpire . '"> <AudienceRestrictionCondition> <Audience>' . htmlspecialchars($audience) . '</Audience> </AudienceRestrictionCondition> </Conditions> <AuthenticationStatement AuthenticationInstant="' . $issueInstant . '" AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:unspecified">' . $subjectNode . ' </AuthenticationStatement> ' . $encodedattributes . ' </Assertion> </Response>'; return $response; }
/** * Load private key from metadata. * * This function loads a private key from a metadata array. It searches for the * following elements: * 'privatekey' Name of a private key file in the cert-directory. * 'privatekey_pass' Password for the private key. * * It returns and array with the following elements: * 'PEM' Data for the private key, in PEM-format * 'password' Password for the private key. * * @param SimpleSAML_Configuration $metadata The metadata array the private key should be loaded from. * @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 Extracted private key, or NULL if no private key is present. */ public static function loadPrivateKey(SimpleSAML_Configuration $metadata, $required = FALSE, $prefix = '') { assert('is_bool($required)'); assert('is_string($prefix)'); $file = $metadata->getString($prefix . 'privatekey', NULL); if ($file === NULL) { /* No private key found. */ if ($required) { throw new Exception('No private key found in metadata.'); } else { return NULL; } } $file = SimpleSAML_Utilities::resolveCert($file); $data = @file_get_contents($file); if ($data === FALSE) { throw new Exception('Unable to load private key from file "' . $file . '"'); } $ret = array('PEM' => $data); if ($metadata->hasValue($prefix . 'privatekey_pass')) { $ret['password'] = $metadata->getString($prefix . 'privatekey_pass'); } return $ret; }
/** * 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')); } }
/** * Build an authentication request based on information in the metadata. * * @param SimpleSAML_Configuration $spMetadata The metadata of the service provider. * @param SimpleSAML_Configuration $idpMetadata The metadata of the identity provider. */ public static function buildAuthnRequest(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata) { $ar = new \SAML2\AuthnRequest(); // get the NameIDPolicy to apply. IdP metadata has precedence. $nameIdPolicy = array(); if ($idpMetadata->hasValue('NameIDPolicy')) { $nameIdPolicy = $idpMetadata->getValue('NameIDPolicy'); } elseif ($spMetadata->hasValue('NameIDPolicy')) { $nameIdPolicy = $spMetadata->getValue('NameIDPolicy'); } if (!is_array($nameIdPolicy)) { // handle old configurations where 'NameIDPolicy' was used to specify just the format $nameIdPolicy = array('Format' => $nameIdPolicy); } $nameIdPolicy_cf = SimpleSAML_Configuration::loadFromArray($nameIdPolicy); $policy = array('Format' => $nameIdPolicy_cf->getString('Format', \SAML2\Constants::NAMEID_TRANSIENT), 'AllowCreate' => $nameIdPolicy_cf->getBoolean('AllowCreate', true)); $spNameQualifier = $nameIdPolicy_cf->getString('SPNameQualifier', false); if ($spNameQualifier !== false) { $policy['SPNameQualifier'] = $spNameQualifier; } $ar->setNameIdPolicy($policy); $ar->setForceAuthn($spMetadata->getBoolean('ForceAuthn', FALSE)); $ar->setIsPassive($spMetadata->getBoolean('IsPassive', FALSE)); $protbind = $spMetadata->getValueValidate('ProtocolBinding', array(\SAML2\Constants::BINDING_HTTP_POST, \SAML2\Constants::BINDING_HOK_SSO, \SAML2\Constants::BINDING_HTTP_ARTIFACT, \SAML2\Constants::BINDING_HTTP_REDIRECT), \SAML2\Constants::BINDING_HTTP_POST); /* Shoaib - setting the appropriate binding based on parameter in sp-metadata defaults to HTTP_POST */ $ar->setProtocolBinding($protbind); $ar->setIssuer($spMetadata->getString('entityid')); $ar->setAssertionConsumerServiceIndex($spMetadata->getInteger('AssertionConsumerServiceIndex', NULL)); $ar->setAttributeConsumingServiceIndex($spMetadata->getInteger('AttributeConsumingServiceIndex', NULL)); if ($spMetadata->hasValue('AuthnContextClassRef')) { $accr = $spMetadata->getArrayizeString('AuthnContextClassRef'); $comp = $spMetadata->getValueValidate('AuthnContextComparison', array(\SAML2\Constants::COMPARISON_EXACT, \SAML2\Constants::COMPARISON_MINIMUM, \SAML2\Constants::COMPARISON_MAXIMUM, \SAML2\Constants::COMPARISON_BETTER), \SAML2\Constants::COMPARISON_EXACT); $ar->setRequestedAuthnContext(array('AuthnContextClassRef' => $accr, 'Comparison' => $comp)); } self::addRedirectSign($spMetadata, $idpMetadata, $ar); return $ar; }
/** * Build an authentication request based on information in the metadata. * * @param SimpleSAML_Configuration $spMetadata The metadata of the service provider. * @param SimpleSAML_Configuration $idpMetadata The metadata of the identity provider. */ public static function buildAuthnRequest(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata) { $ar = new SAML2_AuthnRequest(); if ($spMetadata->hasValue('NameIDPolicy')) { $nameIdPolicy = $spMetadata->getString('NameIDPolicy', NULL); } else { $nameIdPolicy = $spMetadata->getString('NameIDFormat', SAML2_Const::NAMEID_TRANSIENT); } if ($nameIdPolicy !== NULL) { $ar->setNameIdPolicy(array('Format' => $nameIdPolicy, 'AllowCreate' => TRUE)); } $ar->setForceAuthn($spMetadata->getBoolean('ForceAuthn', FALSE)); $ar->setIsPassive($spMetadata->getBoolean('IsPassive', FALSE)); $protbind = $spMetadata->getValueValidate('ProtocolBinding', array(SAML2_Const::BINDING_HTTP_POST, SAML2_Const::BINDING_HOK_SSO, SAML2_Const::BINDING_HTTP_ARTIFACT, SAML2_Const::BINDING_HTTP_REDIRECT), SAML2_Const::BINDING_HTTP_POST); /* Shoaib - setting the appropriate binding based on parameter in sp-metadata defaults to HTTP_POST */ $ar->setProtocolBinding($protbind); $ar->setIssuer($spMetadata->getString('entityid')); $ar->setAssertionConsumerServiceIndex($spMetadata->getInteger('AssertionConsumerServiceIndex', NULL)); $ar->setAttributeConsumingServiceIndex($spMetadata->getInteger('AttributeConsumingServiceIndex', NULL)); if ($spMetadata->hasValue('AuthnContextClassRef')) { $accr = $spMetadata->getArrayizeString('AuthnContextClassRef'); $ar->setRequestedAuthnContext(array('AuthnContextClassRef' => $accr)); } self::addRedirectSign($spMetadata, $idpMetadata, $ar); return $ar; }
/** * 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; }
/** * Build an authentication request based on information in the metadata. * * @param SimpleSAML_Configuration $spMetadata The metadata of the service provider. * @param SimpleSAML_Configuration $idpMetadata The metadata of the identity provider. */ public static function buildAuthnRequest(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata) { $ar = new SAML2_AuthnRequest(); if ($spMetadata->hasValue('NameIDPolicy')) { $nameIdPolicy = $spMetadata->getString('NameIDPolicy', NULL); } else { $nameIdPolicy = $spMetadata->getString('NameIDFormat', SAML2_Const::NAMEID_TRANSIENT); } if ($nameIdPolicy !== NULL) { $ar->setNameIdPolicy(array('Format' => $nameIdPolicy, 'AllowCreate' => TRUE)); } $ar->setIssuer($spMetadata->getString('entityid')); $ar->setDestination($idpMetadata->getString('SingleSignOnService')); $ar->setForceAuthn($spMetadata->getBoolean('ForceAuthn', FALSE)); $ar->setIsPassive($spMetadata->getBoolean('IsPassive', FALSE)); if ($spMetadata->hasValue('AuthnContextClassRef')) { $accr = $spMetadata->getArrayizeString('AuthnContextClassRef'); $ar->setRequestedAuthnContext(array('AuthnContextClassRef' => $accr)); } self::addRedirectSign($spMetadata, $idpMetadata, $ar); return $ar; }
/** * 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; }