/** * For XML-RPC - we want to check for enc / sigs * * @return $xml */ protected function modify_payload() { global $HTTP_RAW_POST_DATA; $xml = null; // check for encryption and signatures if ($this->authmethod == WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN) { // we need the token so that we can find the key if (!($dbtoken = get_record('external_tokens', 'token', $this->token, 'tokentype', EXTERNAL_TOKEN_PERMANENT))) { // log failed login attempts throw new WebserviceAccessException(get_string('invalidtoken', 'auth.webservice')); } // is WS-Security active ? if ($dbtoken->wssigenc) { $this->publickey = $dbtoken->publickey; } } else { if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) { // get the user $user = get_record('usr', 'username', $this->username); if (empty($user)) { throw new WebserviceAccessException(get_string('wrongusernamepassword', 'auth.webservice')); } // get the institution from the external user $ext_user = get_record('external_services_users', 'userid', $user->id); if (empty($ext_user)) { throw new WebserviceAccessException(get_string('wrongusernamepassword', 'auth.webservice')); } // is WS-Security active ? if ($ext_user->wssigenc) { $this->publickey = $ext_user->publickey; } } } // only both if we can find a public key if (!empty($this->publickey)) { // A singleton provides our site's SSL info require_once get_config('docroot') . 'api/xmlrpc/lib.php'; $HTTP_RAW_POST_DATA = file_get_contents('php://input'); $openssl = OpenSslRepo::singleton(); $payload = $HTTP_RAW_POST_DATA; $this->payload_encrypted = false; $this->payload_signed = false; try { $xml = new SimpleXMLElement($payload); } catch (Exception $e) { throw new XmlrpcServerException('Payload is not a valid XML document', 6001); } // Cascading switch. Kinda. try { if ($xml->getName() == 'encryptedMessage') { $this->payload_encrypted = true; $payload = xmlenc_envelope_strip($xml); } if ($xml->getName() == 'signedMessage') { $this->payload_signed = true; $payload = xmldsig_envelope_strip($xml); } $xml = $payload; } catch (CryptException $e) { if ($e->getCode() == 7025) { // The key they used to contact us is old, respond with the new key correctly // This sucks. Error handling of our mnet code needs to improve ob_start(); xmlrpc_error($e->getMessage(), $e->getCode()); $response = ob_get_contents(); ob_end_clean(); // Sign and encrypt our response, even though we don't know if the // request was signed and encrypted $response = xmldsig_envelope($response); $response = xmlenc_envelope($response, $this->publickey); $xml = $response; } } } // if XML has been grabbed already then it must be turned into a request object if ($xml) { $request = new Zend_XmlRpc_Request(); $request->loadXML($xml); $xml = $request; } return $xml; }
// A singleton provides our site's SSL info $openssl = OpenSslRepo::singleton(); $payload = $HTTP_RAW_POST_DATA; $payload_encrypted = false; $payload_signed = false; try { $xml = new SimpleXMLElement($payload); } catch (Exception $e) { throw new XmlrpcServerException('Payload is not a valid XML document', 6001); } // Cascading switch. Kinda. try { if ($xml->getName() == 'encryptedMessage') { $payload_encrypted = true; $REMOTEWWWROOT = (string) $xml->wwwroot; $payload = xmlenc_envelope_strip($xml); } if ($xml->getName() == 'signedMessage') { $payload_signed = true; $REMOTEWWWROOT = (string) $xml->wwwroot; $payload = xmldsig_envelope_strip($xml); } } catch (CryptException $e) { if ($e->getCode() == 7025) { // The key they used to contact us is old, respond with the new key correctly // This sucks. Error handling of our mnet code needs to improve ob_start(); xmlrpc_error($e->getMessage(), $e->getCode()); $response = ob_get_contents(); ob_end_clean(); // Sign and encrypt our response, even though we don't know if the
function send($wwwroot, $use_cached_peer = true) { $this->peer = get_peer($wwwroot, $use_cached_peer); $this->response = ''; $URL = $this->peer->wwwroot . $this->peer->application->xmlrpcserverurl; $this->requesttext = xmlrpc_encode_request($this->method, $this->params, array("encoding" => "utf-8")); $this->signedrequest = xmldsig_envelope($this->requesttext); $this->encryptedrequest = xmlenc_envelope($this->signedrequest, $this->peer->certificate); $config = array(CURLOPT_URL => $URL, CURLOPT_TIMEOUT => $this->timeout, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_USERAGENT => 'Mahara', CURLOPT_POSTFIELDS => $this->encryptedrequest, CURLOPT_HTTPHEADER => array("Content-Type: text/xml charset=UTF-8", 'Expect: '), CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => 0); $result = mahara_http_request($config); $timestamp_send = time(); $this->rawresponse = $result->data; $response_code = $result->info['http_code']; $response_code_prefix = substr($response_code, 0, 1); if ('2' != $response_code_prefix) { if ('4' == $response_code_prefix) { throw new XmlrpcClientException('Client error code: ' . $response_code); } else { if ('5' == $response_code_prefix) { throw new XmlrpcClientException('An error occurred at the remote server. Code: ' . $response_code); } } } $timestamp_receive = time(); $remote_timestamp = null; $curl_errno = $result->errno; if ($curl_errno || $this->rawresponse == false) { throw new XmlrpcClientException('Curl error: ' . $curl_errno . ': ' . $result->error); return false; } try { $xml = new SimpleXMLElement(trim($this->rawresponse)); } catch (Exception $e) { log_debug($this->rawresponse); throw new XmlrpcClientException('Payload is not a valid XML document (payload is above)', 6001); } try { if ($xml->getName() == 'encryptedMessage') { $payload_encrypted = true; $wwwroot = (string) $xml->wwwroot; // Strip encryption, using an older code is OK, because we're the client. // The server is able to respond with the correct key, be we're not $payload = xmlenc_envelope_strip($xml, true); } if ($xml->getName() == 'signedMessage') { $payload_signed = true; $remote_timestamp = $xml->timestamp; $payload = xmldsig_envelope_strip($xml); } } catch (CryptException $e) { throw new XmlrpcClientException("An error occurred while decrypting a message sent by {$wwwroot}. Unable to authenticate the user."); } if ($xml->getName() == 'methodResponse') { $this->response = xmlrpc_decode($payload); // Margin of error is the time it took the request to complete. $margin_of_error = $timestamp_receive - $timestamp_send; // Guess the time gap between sending the request and the remote machine // executing the time() function. Marginally better than nothing. $hysteresis = $margin_of_error / 2; if (!empty($remote_timestamp)) { $remote_timestamp = $remote_timestamp - $hysteresis; $time_offset = $remote_timestamp - $timestamp_send; if ($time_offset > self::get_max_server_time_difference()) { throw new XmlrpcClientException('Time drift (' . $margin_of_error . ', ' . $time_offset . ') is too large.'); } } if (is_array($this->response) && array_key_exists('faultCode', $this->response)) { if ($this->response['faultCode'] == 7025) { log_info('Remote application has sent us a new public key'); // The remote application sent back a new public key, the // old one must have expired if (array_key_exists('faultString', $this->response)) { $details = openssl_x509_parse($this->response['faultString']); if (isset($details['validTo_time_t'])) { $updateobj = (object) array('publickey' => $this->response['faultString'], 'publickeyexpires' => $details['validTo_time_t']); $whereobj = (object) array('wwwroot' => $wwwroot); update_record('host', $updateobj, $whereobj); log_info('New key has been imported. Valid until ' . date('Y/m/d h:i:s', $details['validTo_time_t'])); // Send request again. But don't use the cached // peer, look it up again now we've changed the // public key $this->send($wwwroot, false); } else { throw new XmlrpcClientException('Could not parse new public key'); } } else { throw new XmlrpcClientException('Remote site claims to have sent a public key, but they LIE'); } } throw new XmlrpcClientException('Unknown error occurred: ' . $this->response['faultCode'] . ': ' . $this->response['faultString']); } // Clean up so object can be re-used. $this->requesttext = ''; $this->signedrequest = ''; $this->encryptedrequest = ''; $this->params = array(); $this->method = ''; return true; } else { throw new XmlrpcClientException('Unrecognized XML document form: ' . $payload); } }
/** * For SOAP - we want to inspect for auth headers * and do decrypt / sigs * * @return $xml */ protected function modify_payload() { $xml = null; // don't do any of this if we are in the WSDL phase if (param_boolean('wsdl', 0)) { return $xml; } // check for encryption and signatures if ($this->authmethod == WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN) { // we need the token so that we can find the key if (!($dbtoken = get_record('external_tokens', 'token', $this->token, 'tokentype', EXTERNAL_TOKEN_PERMANENT))) { // log failed login attempts throw new WebserviceAccessException(get_string('invalidtoken', 'auth.webservice')); } // is WS-Security active ? if ($dbtoken->wssigenc) { $this->publickey = $dbtoken->publickey; } } else { if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME && !empty($this->username)) { // get the user $user = get_record('usr', 'username', $this->username); if (empty($user)) { throw new WebserviceAccessException(get_string('wrongusernamepassword', 'auth.webservice')); } // get the institution from the external user $ext_user = get_record('external_services_users', 'userid', $user->id); if (empty($ext_user)) { throw new WebserviceAccessException(get_string('wrongusernamepassword', 'auth.webservice')); } // is WS-Security active ? if ($ext_user->wssigenc) { $this->publickey = $ext_user->publickey; } } } // only both if we can find a public key if (!empty($this->publickey)) { // A singleton provides our site's SSL info require_once get_config('docroot') . 'api/xmlrpc/lib.php'; $HTTP_RAW_POST_DATA = file_get_contents('php://input'); $openssl = OpenSslRepo::singleton(); $payload = $HTTP_RAW_POST_DATA; $this->payload_encrypted = false; $this->payload_signed = false; try { $xml = new SimpleXMLElement($payload); } catch (Exception $e) { throw new XmlrpcServerException('Payload is not a valid XML document', 6001); } // Cascading switch. Kinda. try { if ($xml->getName() == 'encryptedMessage') { $this->payload_encrypted = true; $payload = xmlenc_envelope_strip($xml); } if ($xml->getName() == 'signedMessage') { $this->payload_signed = true; $signature = base64_decode($xml->Signature->SignatureValue); $payload = base64_decode($xml->object); $timestamp = $xml->timestamp; // Does the signature match the data and the public cert? $signature_verified = openssl_verify($payload, $signature, $this->publickey); if ($signature_verified == 1) { // Parse the XML try { $xml = new SimpleXMLElement($payload); } catch (Exception $e) { throw new MaharaException('Signed payload is not a valid XML document', 6007); } } else { throw new MaharaException('An error occurred while trying to verify your message signature', 6004); } } $xml = $payload; } catch (CryptException $e) { if ($e->getCode() == 7025) { // The key they used to contact us is old, respond with the new key correctly // This sucks. Error handling of our mnet code needs to improve ob_start(); xmlrpc_error($e->getMessage(), $e->getCode()); $response = ob_get_contents(); ob_end_clean(); // Sign and encrypt our response, even though we don't know if the // request was signed and encrypted $response = xmldsig_envelope($response); $response = xmlenc_envelope($response, $this->publickey); $xml = $response; } } } // standard auth if (!isset($_REQUEST['wsusername']) && $this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME || !empty($this->publickey)) { // wsse auth // we may already have the xml if sig/enc if (empty($xml)) { $xml = file_get_contents('php://input'); } $dom = new DOMDocument(); if (strlen($xml) == 0 || !$dom->loadXML($xml)) { require_once 'Zend/Soap/Server/Exception.php'; throw new Zend_Soap_Server_Exception('Invalid XML'); } else { // now hunt for the user and password from the headers $xpath = new DOMXpath($dom); $xpath->registerNamespace('wsse', 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'); if ($q = $xpath->query("//wsse:Security/wsse:UsernameToken/wsse:Username/text()", $dom)) { if ($q->item(0)) { $this->username = (string) $q->item(0)->data; $this->password = (string) $xpath->query("//wsse:Security/wsse:UsernameToken/wsse:Password/text()", $dom)->item(0)->data; $this->authmethod = WEBSERVICE_AUTHMETHOD_USERNAME; } } } } return $xml; }