MDL16286 - redefine function boundaries.
authorpeterbulmer <peterbulmer>
Sun, 28 Sep 2008 04:19:04 +0000 (04:19 +0000)
committerpeterbulmer <peterbulmer>
Sun, 28 Sep 2008 04:19:04 +0000 (04:19 +0000)
Better defined what the decrypt / signature functions do & brought a lot of the error-exits together

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

mnet/remote_client.php
mnet/xmlrpc/server.php

index 9a5aff499ed531f6c73315210be0a18b39d4f401..f000766bb501aa6f64648256e2d85f1c358fe438 100644 (file)
@@ -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;
     }
index a4b758fdaea9ba8537df2165c02158f4f9f3abf6..fd20d2fb3e0dd423bbe7c7f2655ff71e2f6bce35 100644 (file)
@@ -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();