From 8d60e9427d1709979d1cdbe58f45b41ab1537e8a Mon Sep 17 00:00:00 2001 From: peterbulmer Date: Sun, 28 Sep 2008 04:19:04 +0000 Subject: [PATCH] MDL16286 - redefine function boundaries. Better defined what the decrypt / signature functions do & brought a lot of the error-exits together Author: Peter Bulmer --- mnet/remote_client.php | 19 +++++ mnet/xmlrpc/server.php | 172 ++++++++++++++++++++++------------------- 2 files changed, 111 insertions(+), 80 deletions(-) diff --git a/mnet/remote_client.php b/mnet/remote_client.php index 9a5aff499e..f000766bb5 100644 --- a/mnet/remote_client.php +++ b/mnet/remote_client.php @@ -17,15 +17,34 @@ class mnet_remote_client extends mnet_peer { var $static_location = false; var $request_was_encrypted = false; var $request_was_signed = false; + var $signatureok = false; // True if we have successfully verified that the request was signed by an established peer + var $pushkey = false; // True if we need to tell the remote peer about our current public key + var $useprivatekey = ''; // The private key we should use to sign pushkey response function was_encrypted() { $this->request_was_encrypted = true; } + /* Record private key to use in pushkey response + * Called when we have decrypted a request using an old (but still acceptable) keypair + * @param $keyresource the private key we should use to sign the response. + */ + function encrypted_to($keyresource) { + $this->useprivatekey = $keyresource; + } + + function set_pushkey() { + $this->pushkey = true; + } + function was_signed() { $this->request_was_signed = true; } + function signature_verified() { + $this->signatureok = true; + } + function object_to_call($object) { $this->object_to_call = $object; } diff --git a/mnet/xmlrpc/server.php b/mnet/xmlrpc/server.php index a4b758fdae..fd20d2fb3e 100644 --- a/mnet/xmlrpc/server.php +++ b/mnet/xmlrpc/server.php @@ -36,21 +36,57 @@ if (!empty($CFG->mnet_rpcdebug)) { trigger_error($HTTP_RAW_POST_DATA); } +if (!isset($_SERVER)) { + exit(mnet_server_fault(712, "phperror")); +} + + // New global variable which ONLY gets set in this server page, so you know that // if you've been called by a remote Moodle, this should be set: $MNET_REMOTE_CLIENT = new mnet_remote_client(); -$plaintextmessage = mnet_server_decrypt_data($HTTP_RAW_POST_DATA); -$xmlrpcrequest = mnet_server_check_signature($plaintextmessage); +$plaintextmessage = mnet_server_strip_encryption($HTTP_RAW_POST_DATA); +$xmlrpcrequest = mnet_server_strip_signature($plaintextmessage); + +if($this->pushkey == true) { + // The peer used one of our older public keys, we will return a + // signed/encrypted error message containing our new public key + // Sign message with our old key, and encrypt to the peer's private key. + exit(mnet_server_fault_xml(7025, $MNET->public_key, $this->useprivatekey)); +} +// Have a peek at what the request would be if we were to process it +$params = xmlrpc_decode_request($xmlrpcrequest, $method); + +// One of three conditions need to be met before we continue processing this request: +// 1. Request is properly encrypted and signed +// 2. Request is for a keyswap (we don't mind enencrypted or unsigned requests for a public key) +// 3. Request is properly signed and we're happy with it being unencrypted +if ((($MNET_REMOTE_CLIENT->request_was_encrypted == true) && ($MNET_REMOTE_CLIENT->signatureok == true)) + || (($method == 'system.keyswap') || ($method == 'system/keyswap')) + || (($MNET_REMOTE_CLIENT->signatureok == true) && ($MNET_REMOTE_CLIENT->plaintext_is_ok() == true))) { + $response = mnet_server_dispatch($xmlrpcrequest); +} else { + if (($MNET_REMOTE_CLIENT->request_was_encrypted == false) && ($MNET_REMOTE_CLIENT->plaintext_is_ok() == false)) { + exit(mnet_server_fault(7021, 'forbidden-transport')); + } + + if ($MNET_REMOTE_CLIENT->request_was_signed == false) { + // Request was not signed + exit(mnet_server_fault(711, 'verifysignature-error')); + } + + if ($MNET_REMOTE_CLIENT->signatureok == false) { + // We were unable to verify the signature + exit(mnet_server_fault(710, 'verifysignature-invalid')); + } +} + if (!empty($CFG->mnet_rpcdebug)) { trigger_error("XMLRPC Payload"); trigger_error(print_r($xmlrpcrequest,1)); } -// Parse and action the XML-RPC payload -$response = mnet_server_dispatch($xmlrpcrequest); - /** * -----XML-Envelope--------------------------------- * | | @@ -76,20 +112,20 @@ $response = mnet_server_dispatch($xmlrpcrequest); * */ -/* Decrypt supplied data using our private key. +/* Strip encryption envelope (if present) and decrypt data * * @param string $HTTP_RAW_POST_DATA The XML that the client sent - * @return string XML envelope containing XMLRPC request and remote peer signature + * @return string XML with any encryption envolope removed */ -function mnet_server_decrypt_data($HTTP_RAW_POST_DATA) { +function mnet_server_strip_encryption($HTTP_RAW_POST_DATA) { global $MNET, $MNET_REMOTE_CLIENT; - if (!isset($_SERVER)) { - exit(mnet_server_fault(712, "phperror")); - } - $crypt_parser = new mnet_encxml_parser(); $crypt_parser->parse($HTTP_RAW_POST_DATA); + if (!$crypt_parser->payload_encrypted) { + return $HTTP_RAW_POST_DATA; + } + // Make sure we know who we're talking to $host_record_exists = $MNET_REMOTE_CLIENT->set_wwwroot($crypt_parser->remote_wwwroot); @@ -97,23 +133,6 @@ function mnet_server_decrypt_data($HTTP_RAW_POST_DATA) { exit(mnet_server_fault(7020, 'wrong-wwwroot', $crypt_parser->remote_wwwroot)); } - if (!$crypt_parser->payload_encrypted) { - //In most situations payload_unencrypted represents an error - $params = xmlrpc_decode_request($HTTP_RAW_POST_DATA, $method); - if (($method != 'system.keyswap') - && ($method != 'system/keyswap') - && ($MNET_REMOTE_CLIENT->plaintext_is_ok() == false)) { - exit(mnet_server_fault(7021, 'forbidden-transport')); - } - // Looks like plaintext is ok. It is assumed that a plaintext call: - // 1. Came from a trusted host on your local network - // 2. Is *not* from a Moodle - otherwise why skip encryption/signing? - // 3. Is free to execute ANY function in Moodle - // 4. Cannot execute any methods (as it can't instantiate a class first) - // To execute a method, you'll need to create a wrapper function that first - // instantiates the class, and then calls the method. - return $HTTP_RAW_POST_DATA; - } // This key is symmetric, and is itself encrypted. Can be decrypted using our private key $key = array_pop($crypt_parser->cipher); // This data is symmetrically encrypted, can be decrypted using the above key @@ -124,66 +143,65 @@ function mnet_server_decrypt_data($HTTP_RAW_POST_DATA) { // &$payload $isOpen = openssl_open(base64_decode($data), $payload, base64_decode($key), $MNET->get_private_key()); + if ($isOpen) { + $MNET_REMOTE_CLIENT->was_encrypted(); + return $payload; + } - if (!$isOpen) { - // Decryption failed... let's try our archived keys - $openssl_history = get_config('mnet', 'openssl_history'); - if(empty($openssl_history)) { - $openssl_history = array(); - set_config('openssl_history', serialize($openssl_history), 'mnet'); - } else { - $openssl_history = unserialize($openssl_history); - } - foreach($openssl_history as $keyset) { - $keyresource = openssl_pkey_get_private($keyset['keypair_PEM']); - $isOpen = openssl_open(base64_decode($data), $payload, base64_decode($key), $keyresource); - if ($isOpen) { - // It's an older code, sir, but it checks out - - // The peer used one of our public keys that have expired, we will return a - // signed/encrypted error message containing our new public key - // Sign message with our old key, and encrypt to the peer's private key. - - // Fabricate 'was_signed' - // Set here so that we sign the response containing the new public key. - $MNET_REMOTE_CLIENT->was_signed(); - - // 'Was_encrypted' is mostly true - // Set here so that the response is encrypted to the remote peer's private key. - $MNET_REMOTE_CLIENT->was_encrypted(); - - // nb 'srvr_fault_xml' used to avoid use of get_string on our new public_key - exit(mnet_server_fault_xml(7025, $MNET->public_key, $keyresource)); - } + // Decryption failed... let's try our archived keys + $openssl_history = get_config('mnet', 'openssl_history'); + if(empty($openssl_history)) { + $openssl_history = array(); + set_config('openssl_history', serialize($openssl_history), 'mnet'); + } else { + $openssl_history = unserialize($openssl_history); + } + foreach($openssl_history as $keyset) { + $keyresource = openssl_pkey_get_private($keyset['keypair_PEM']); + $isOpen = openssl_open(base64_decode($data), $payload, base64_decode($key), $keyresource); + if ($isOpen) { + // It's an older code, sir, but it checks out + + $MNET_REMOTE_CLIENT->was_encrypted(); + $MNET_REMOTE_CLIENT->encrypted_to($keyresource); + $MNET_REMOTE_CLIENT->set_pushkey(); + return $payload; } } - if (!$isOpen) { - exit(mnet_server_fault(7023, 'encryption-invalid')); - } - $MNET_REMOTE_CLIENT->was_encrypted(); - return $payload; + //If after all that we still couldn't decrypt the message, error out. + exit(mnet_server_fault(7023, 'encryption-invalid')); } -/* Check signature for supplied message using our record of remote peer's public key. +/* Strip signature envelope (if present), try to verify any signature using our record of remote peer's public key. * * @param string $plaintextmessage XML envelope containing XMLRPC request and signature * @return string XMLRPC request */ -function mnet_server_check_signature($plaintextmessage) { +function mnet_server_strip_signature($plaintextmessage) { global $MNET, $MNET_REMOTE_CLIENT; $sig_parser = new mnet_encxml_parser(); $sig_parser->parse($plaintextmessage); - // Get our record of the peer's public key - $certificate = $MNET_REMOTE_CLIENT->public_key; - - if ($certificate == false) { - exit(mnet_server_fault(709, 'nosuchpublickey')); + if ($sig_parser->signature == '') { + return $plaintextmessage; } + // Record that the request was signed in some way + $MNET_REMOTE_CLIENT->was_signed(); + + // Load any information we have about this mnet peer + $MNET_REMOTE_CLIENT->set_wwwroot($crypt_parser->remote_wwwroot); + $payload = base64_decode($sig_parser->data_object); $signature = base64_decode($sig_parser->signature); + $certificate = $MNET_REMOTE_CLIENT->public_key; + + // If we don't have any certificate for the host, but don't try to check the signature + // Just return the parsed request + if ($certificate == false) { + return $payload; + } // Does the signature match the data and the public cert? $signature_verified = openssl_verify($payload, $signature, $certificate); @@ -193,10 +211,8 @@ function mnet_server_check_signature($plaintextmessage) { $currkey = mnet_get_public_key($MNET_REMOTE_CLIENT->wwwroot, $MNET_REMOTE_CLIENT->application); // If the key the remote peer is currently publishing is different to $certificate if($currkey != $certificate) { - // If we can't get the server's new key through trusted means - if(!$MNET_REMOTE_CLIENT->refresh_key()){ - exit(mnet_server_fault(7026, 'verifysignature-invalid')); - } + // Try and get the server's new key through trusted means + $MNET_REMOTE_CLIENT->refresh_key(); // If we did manage to re-key, try to verify the signature again using the new public key. $certificate = $MNET_REMOTE_CLIENT->public_key; $signature_verified = openssl_verify($payload, $signature, $certificate); @@ -204,12 +220,8 @@ function mnet_server_check_signature($plaintextmessage) { } if ($signature_verified == 1) { - $MNET_REMOTE_CLIENT->was_signed(); + $MNET_REMOTE_CLIENT->signature_verified(); $MNET_REMOTE_CLIENT->touch(); - } elseif ($signature_verified == 0) { - exit(mnet_server_fault(710, 'verifysignature-invalid')); - } else { - exit(mnet_server_fault(711, 'verifysignature-error')); } $sig_parser->free_resource(); -- 2.39.5