From 5f6b28faf25fed4f7403b5f1db4696f7c8be48cf Mon Sep 17 00:00:00 2001 From: peterbulmer Date: Sun, 7 Sep 2008 03:18:53 +0000 Subject: [PATCH] MDL-16286 separate strip wrappers() to decrypt() and check_sig() additionally unfold logic inverstion around encrypted if block To view this diff more effectively, consider setting 'ignore whitespace' Author: Peter Bulmer --- mnet/xmlrpc/server.php | 201 ++++++++++++++++++----------------------- 1 file changed, 86 insertions(+), 115 deletions(-) diff --git a/mnet/xmlrpc/server.php b/mnet/xmlrpc/server.php index e337d0c82a..7b6db4b53f 100644 --- a/mnet/xmlrpc/server.php +++ b/mnet/xmlrpc/server.php @@ -40,64 +40,18 @@ if (!empty($CFG->mnet_rpcdebug)) { // if you've been called by a remote Moodle, this should be set: $MNET_REMOTE_CLIENT = new mnet_remote_client(); -// Peek at the message to see if it's an XML-ENC document. If it is, note that -// the client connection was encrypted, and strip the xml-encryption and -// xml-signature wrappers from the XML-RPC payload -if (strpos(substr($HTTP_RAW_POST_DATA, 0, 100), '')) { - $MNET_REMOTE_CLIENT->was_encrypted(); -// Extract the XML-RPC payload from the XML-ENC and XML-SIG wrappers. - $payload = mnet_server_strip_wrappers($HTTP_RAW_POST_DATA); -} else { - $params = xmlrpc_decode_request($HTTP_RAW_POST_DATA, $method); - if ($method == 'system.keyswap' || - $method == 'system/keyswap') { - - // OK - - } elseif ($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. - $payload = $HTTP_RAW_POST_DATA; -} +$plaintextmessage = mnet_server_decrypt_data($HTTP_RAW_POST_DATA); +$xmlrpcrequest = mnet_server_check_signature($plaintextmessage); if (!empty($CFG->mnet_rpcdebug)) { trigger_error("XMLRPC Payload"); - trigger_error(print_r($payload,1)); + trigger_error(print_r($xmlrpcrequest,1)); } // Parse and action the XML-RPC payload -$response = mnet_server_dispatch($payload); +$response = mnet_server_dispatch($xmlrpcrequest); /** - * Strip the encryption (XML-ENC) and signature (XML-SIG) wrappers and return the XML-RPC payload - * - * IF COMMUNICATION TAKES PLACE OVER UNENCRYPTED HTTP: - * The payload will have been encrypted with a symmetric key. This key will - * itself have been encrypted using your public key. The key is decrypted using - * your private key, and then used to decrypt the XML payload. - * - * IF COMMUNICATION TAKES PLACE OVER UNENCRYPTED HTTP *OR* ENCRYPTED HTTPS: - * In either case, there will be an XML wrapper which contains your XML-RPC doc - * as an object element, a signature for that doc, and various standards- - * compliant info to aid in verifying the signature. - * - * This function parses the encryption wrapper, decrypts the contents, parses - * the signature wrapper, and if the signature matches the payload, it returns - * the payload, which should be an XML-RPC request. - * If there is an error, or the signatures don't match, it echoes an XML-RPC - * error and exits. - * - * See the W3C's {@link http://www.w3.org/TR/xmlenc-core/ XML Encryption Syntax and Processing} - * and {@link http://www.w3.org/TR/2001/PR-xmldsig-core-20010820/ XML-Signature Syntax and Processing} - * guidelines for more detail on the XML. - * * -----XML-Envelope--------------------------------- * | | * | Encrypted-Symmetric-key---------------- | @@ -120,10 +74,14 @@ $response = mnet_server_dispatch($payload); * | | * |________________________________________________| * - * @param string $HTTP_RAW_POST_DATA The XML that the client sent - * @return string The XMLRPC payload. */ -function mnet_server_strip_wrappers($HTTP_RAW_POST_DATA) { + +/* Decrypt supplied data using our private key. + * + * @param string $HTTP_RAW_POST_DATA The XML that the client sent + * @return string XML envelope containing XMLRPC request and remote peer signature + */ +function mnet_server_decrypt_data($HTTP_RAW_POST_DATA) { global $MNET, $MNET_REMOTE_CLIENT; if (!isset($_SERVER)) { exit(mnet_server_fault(712, "phperror")); @@ -139,73 +97,85 @@ function mnet_server_strip_wrappers($HTTP_RAW_POST_DATA) { exit(mnet_server_fault(7020, 'wrong-wwwroot', $crypt_parser->remote_wwwroot)); } - if ($crypt_parser->payload_encrypted) { - - $key = array_pop($crypt_parser->cipher); // This key is Symmetric - $data = array_pop($crypt_parser->cipher); - - $crypt_parser->free_resource(); - - $payload = ''; // Initialize payload var - $push_current_key = false; // True if we need to push a fresh key to the peer - - // &$payload - $isOpen = openssl_open(base64_decode($data), $payload, base64_decode($key), $MNET->get_private_key()); - - 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)); - } - } - } - - if (!$isOpen) { - exit(mnet_server_fault(7023, 'encryption-invalid')); + 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')); } - - if (strpos(substr($payload, 0, 100), '')) { - $MNET_REMOTE_CLIENT->was_signed(); - $sig_parser = new mnet_encxml_parser(); - $sig_parser->parse($payload); + // 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 + $data = array_pop($crypt_parser->cipher); + + $crypt_parser->free_resource(); + $payload = ''; // Initialize payload var + + // &$payload + $isOpen = openssl_open(base64_decode($data), $payload, base64_decode($key), $MNET->get_private_key()); + + 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 { - exit(mnet_server_fault(7022, 'verifysignature-error')); + $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)); + } + } + } - } else { - exit(mnet_server_fault(7024, 'payload-not-encrypted')); + if (!$isOpen) { + exit(mnet_server_fault(7023, 'encryption-invalid')); } + $MNET_REMOTE_CLIENT->was_encrypted(); + return $payload; +} - unset($payload); +/* Check signature for supplied message 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) { + global $MNET, $MNET_REMOTE_CLIENT; + $sig_parser = new mnet_encxml_parser(); + $sig_parser->parse($plaintextmessage); - /** - * Get the certificate (i.e. public key) from the remote server. - */ + // Get our record of the peer's public key $certificate = $MNET_REMOTE_CLIENT->public_key; if ($certificate == false) { @@ -213,12 +183,13 @@ function mnet_server_strip_wrappers($HTTP_RAW_POST_DATA) { } $payload = base64_decode($sig_parser->data_object); + $signature = base64_decode($sig_parser->signature); // Does the signature match the data and the public cert? - $signature_verified = openssl_verify($payload, base64_decode($sig_parser->signature), $certificate); + $signature_verified = openssl_verify($payload, $signature, $certificate); if ($signature_verified == 1) { + $MNET_REMOTE_CLIENT->was_signed(); $MNET_REMOTE_CLIENT->touch(); - // Parse the XML } elseif ($signature_verified == 0) { $currkey = mnet_get_public_key($MNET_REMOTE_CLIENT->wwwroot, $MNET_REMOTE_CLIENT->application); if($currkey != $certificate) { -- 2.39.5