]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-16286 separate strip wrappers() to decrypt() and check_sig()
authorpeterbulmer <peterbulmer>
Sun, 7 Sep 2008 03:18:53 +0000 (03:18 +0000)
committerpeterbulmer <peterbulmer>
Sun, 7 Sep 2008 03:18:53 +0000 (03:18 +0000)
additionally unfold logic inverstion around encrypted if block
To view this diff more effectively, consider setting 'ignore whitespace'

Author: Peter Bulmer <peter.bulmer@catalyst.net.nz>

mnet/xmlrpc/server.php

index e337d0c82a696a6e8a7cbe6ac949ec0a1f08fb14..7b6db4b53f3c13fae062be9790b41a64b39384d1 100644 (file)
@@ -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), '<encryptedMessage>')) {
-    $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), '<signedMessage>')) {
-            $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) {