]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Diaspora plugin is almost there (for remote salmon slaps at least)
authorMikael Nordfeldth <mmn@hethane.se>
Sun, 4 Oct 2015 10:06:48 +0000 (12:06 +0200)
committerMikael Nordfeldth <mmn@hethane.se>
Sun, 4 Oct 2015 10:06:48 +0000 (12:06 +0200)
lib/util.php
plugins/Diaspora/DiasporaPlugin.php
plugins/OStatus/OStatusPlugin.php
plugins/OStatus/lib/magicenvelope.php
plugins/OStatus/lib/salmon.php

index edada03864a37d1781f0a6b47ca0e2780210b08b..64733986d3db8a7fc23d20fdf1090caba0b06637 100644 (file)
@@ -1557,14 +1557,24 @@ function common_root_url($ssl=false)
 }
 
 /**
- * returns $bytes bytes of random data as a hexadecimal string
+ * returns $bytes bytes of raw random data
  */
-function common_random_hexstr($bytes)
+function common_random_rawstr($bytes)
 {
-    $str = @file_exists('/dev/urandom')
+    $rawstr = @file_exists('/dev/urandom')
             ? common_urandom($bytes)
             : common_mtrand($bytes);
 
+    return $rawstr;
+}
+
+/**
+ * returns $bytes bytes of random data as a hexadecimal string
+ */
+function common_random_hexstr($bytes)
+{
+    $str = common_random_rawstr($bytes);
+
     $hexstr = '';
     for ($i = 0; $i < $bytes; $i++) {
         $hexstr .= sprintf("%02x", ord($str[$i]));
index 66a9759f8787078d984914a803c29a9653c9ec74..68f23a438231bdf8369c6f5a4c6f15836c1383b7 100644 (file)
@@ -30,6 +30,12 @@ if (!defined('GNUSOCIAL')) { exit(1); }
  * @maintainer Mikael Nordfeldth <mmn@hethane.se>
  */
 
+// Depends on OStatus of course.
+addPlugin('OStatus');
+
+//Since Magicsig hasn't loaded yet
+require_once('Crypt/AES.php');
+
 class DiasporaPlugin extends Plugin
 {
     const REL_SEED_LOCATION = 'http://joindiaspora.com/seed_location';
@@ -62,4 +68,159 @@ class DiasporaPlugin extends Plugin
 
         return true;
     }
+
+    public function onStartMagicEnvelopeToXML(MagicEnvelope $magic_env, XMLStringer $xs, $flavour=null, Profile $target=null)
+    {
+        // Since Diaspora doesn't use a separate namespace for their "extended"
+        // salmon slap, we'll have to resort to this workaround hack.
+        if ($flavour !== 'diaspora') {
+            return true;
+        }
+
+        // WARNING: This changes the $magic_env contents! Be aware of it.
+
+        /**
+         * https://wiki.diasporafoundation.org/Federation_protocol_overview
+         *
+         * Constructing the encryption header
+         */
+
+        /**
+         * Choose an AES key and initialization vector, suitable for the
+         * aes-256-cbc cipher. I shall refer to this as the “inner key”
+         * and the “inner initialization vector (iv)”.
+         */
+        $inner_key = new Crypt_AES(CRYPT_AES_MODE_CBC);
+        $inner_key->setKeyLength(256);  // set length to 256 bits (could be calculated, but let's be sure)
+        $inner_key->setKey(common_random_rawstr(32));   // 32 bytes from a (pseudo) random source
+        $inner_key->setIV(common_random_rawstr(16));    // 16 bytes is the block length
+
+        /**
+         * Construct the following XML snippet:
+         *  <decrypted_header>
+         *      <iv>((base64-encoded inner iv))</iv>
+         *      <aes_key>((base64-encoded inner key))</aes_key>
+         *      <author>
+         *          <name>Alice Exampleman</name>
+         *          <uri>acct:user@sender.example</uri>
+         *      </author>
+         *  </decrypted_header>
+         */
+        $decrypted_header = sprintf('<decrypted_header><iv>%1$s</iv><aes_key>%2$s</aes_key><author_id>%3$s</author_id></decrypted_header>',
+                                    base64_encode($inner_key->iv),
+                                    base64_encode($inner_key->key),
+                                    $magic_env->getActor()->getAcctUri());
+
+        /**
+         * Construct another AES key and initialization vector suitable
+         * for the aes-256-cbc cipher. I shall refer to this as the
+         * “outer key” and the “outer initialization vector (iv)”.
+         */
+        $outer_key = new Crypt_AES(CRYPT_AES_MODE_CBC);
+        $outer_key->setKeyLength(256);  // set length to 256 bits (could be calculated, but let's be sure)
+        $outer_key->setKey(common_random_rawstr(32));   // 32 bytes from a (pseudo) random source
+        $outer_key->setIV(common_random_rawstr(16));    // 16 bytes is the block length
+
+        /**
+         * Encrypt your <decrypted_header> XML snippet using the “outer key”
+         * and “outer iv” (using the aes-256-cbc cipher). This encrypted
+         * blob shall be referred to as “the ciphertext”. 
+         */
+        $ciphertext = $outer_key->encrypt($decrypted_header);
+
+        /**
+         * Construct the following JSON object, which shall be referred to
+         * as “the outer aes key bundle”:
+         *  {
+         *      "iv": ((base64-encoded AES outer iv)),
+         *      "key": ((base64-encoded AES outer key))
+         *  }
+         */
+        $outer_bundle = json_encode(array(
+                                'iv' => base64_encode($outer_key->iv),
+                                'key' => base64_encode($outer_key->key),
+                            ));
+        /**
+         * Encrypt the “outer aes key bundle” with Bob’s RSA public key.
+         * I shall refer to this as the “encrypted outer aes key bundle”.
+         */
+        $key_fetcher = new MagicEnvelope();
+        $remote_keys = $key_fetcher->getKeyPair($target, true); // actually just gets the public key
+        $enc_outer = $remote_keys->publicKey->encrypt($outer_bundle);
+
+        /**
+         * Construct the following JSON object, which I shall refer to as
+         * the “encrypted header json object”:
+         *  {
+         *      "aes_key": ((base64-encoded encrypted outer aes key bundle)),
+         *      "ciphertext": ((base64-encoded ciphertextm from above))
+         *  }
+         */
+        $enc_header = json_encode(array(
+                            'aes_key' => base64_encode($enc_outer),
+                            'ciphertext' => base64_encode($ciphertext),
+                        ));
+
+        /**
+         * Construct the xml snippet:
+         *  <encrypted_header>((base64-encoded encrypted header json object))</encrypted_header>
+         */
+        $xs->element('encrypted_header', null, base64_encode($enc_header));
+
+        /**
+         * In order to prepare the payload message for inclusion in your
+         * salmon slap, you will:
+         *
+         * 1. Encrypt the payload message using the aes-256-cbc cipher and
+         *      the “inner encryption key” and “inner encryption iv” you
+         *      chose earlier.
+         * 2. Base64-encode the encrypted payload message.
+         */
+        $payload = $inner_key->encrypt($magic_env->getData());
+        $magic_env->signMessage(base64_encode($payload), 'application/xml');
+
+
+        // Since we have to change the content of me:data we'll just write the
+        // whole thing from scratch. We _could_ otherwise have just manipulated
+        // that element and added the encrypted_header in the EndMagicEnvelopeToXML event.
+        $xs->elementStart('me:env', array('xmlns:me' => MagicEnvelope::NS));
+        $xs->element('me:data', array('type' => $magic_env->getDataType()), $magic_env->getData());
+        $xs->element('me:encoding', null, $magic_env->getEncoding());
+        $xs->element('me:alg', null, $magic_env->getSignatureAlgorithm());
+        $xs->element('me:sig', null, $magic_env->getSignature());
+        $xs->elementEnd('me:env');
+
+        return false;
+    }
+
+    public function onSalmonSlap($endpoint_uri, MagicEnvelope $magic_env, Profile $target=null)
+    {
+        $envxml = $magic_env->toXML($target, 'diaspora');
+
+        // Diaspora wants another POST format (base64url-encoded POST variable 'xml')
+        $headers = array('Content-Type: application/x-www-form-urlencoded');
+
+        // Another way to distinguish Diaspora from GNU social is that a POST with
+        // $headers=array('Content-Type: application/magic-envelope+xml') would return
+        // HTTP status code 422 Unprocessable Entity, at least as of 2015-10-04.
+        try {
+            $client = new HTTPClient();
+            $client->setBody('xml=' . Magicsig::base64_url_encode($envxml));
+            $response = $client->post($endpoint_uri, $headers);
+        } catch (HTTP_Request2_Exception $e) {
+            common_log(LOG_ERR, "Diaspora-flavoured Salmon post to $endpoint_uri failed: " . $e->getMessage());
+            return false;
+        }
+
+        // 200 OK is the best response
+        // 202 Accepted is what we get from Diaspora for example
+        if (!in_array($response->getStatus(), array(200, 202))) {
+            common_log(LOG_ERR, sprintf('Salmon (from profile %d) endpoint %s returned status %s: %s',
+                                $user->id, $endpoint_uri, $response->getStatus(), $response->getBody()));
+            return true;
+        }
+
+        // Success!
+        return false;
+    }
 }
index 24e877e262f82c4a782069dd262ae90202c0c8e3..5b7147ebee661ae79999574e1be4a7c579c5b4c6 100644 (file)
@@ -1352,9 +1352,9 @@ class OStatusPlugin extends Plugin
         return true;
     }
 
-    public function onSalmonSlap($endpoint_uri, MagicEnvelope $magic_env)
+    public function onSalmonSlap($endpoint_uri, MagicEnvelope $magic_env, Profile $target=null)
     {
-        $envxml = $magic_env->toXML();
+        $envxml = $magic_env->toXML($target);
 
         $headers = array('Content-Type: application/magic-envelope+xml');
 
index e6b068c92447dc01ce5abb4e156544783f76090b..e96862bbadc0d03a7d7f221a8d2b07c63672bfb9 100644 (file)
@@ -205,10 +205,10 @@ class MagicEnvelope
      *
      * @return string representation of XML document
      */
-    public function toXML($flavour=null) {
+    public function toXML(Profile $target=null, $flavour=null) {
         $xs = new XMLStringer();
         $xs->startXML();    // header, to point out it's not HTML or anything...
-        if (Event::handle('StartMagicEnvelopeToXML', array($this, $xs, $flavour))) {
+        if (Event::handle('StartMagicEnvelopeToXML', array($this, $xs, $flavour, $target))) {
             // fall back to our default, normal Magic Envelope XML.
             // the $xs element _may_ have had elements added, or could get in the end event
             $xs->elementStart('me:env', array('xmlns:me' => self::NS));
@@ -218,7 +218,7 @@ class MagicEnvelope
             $xs->element('me:sig', null, $this->getSignature());
             $xs->elementEnd('me:env');
 
-            Event::handle('EndMagicEnvelopeToXML', array($this, $xs, $flavour));
+            Event::handle('EndMagicEnvelopeToXML', array($this, $xs, $flavour, $target));
         }
         return $xs->getString();
     }
@@ -266,6 +266,9 @@ class MagicEnvelope
 
     public function getSignature()
     {
+        if (empty($this->sig)) {
+            throw new ServerException('You must first call signMessage before getSignature');
+        }
         return $this->sig;
     }
 
@@ -274,6 +277,11 @@ class MagicEnvelope
         return $this->alg;
     }
 
+    public function getData()
+    {
+        return $this->data;
+    }
+
     public function getDataType()
     {
         return $this->data_type;
index 15ed123eed7e33e34b63ae4f4492a020b0e9d38e..cdc8e02d77af23be52f011738835f77b61477e91 100644 (file)
@@ -46,7 +46,7 @@ class Salmon
      * @param User $user local user profile whose keys we sign with
      * @return boolean success
      */
-    public static function post($endpoint_uri, $xml, User $user)
+    public static function post($endpoint_uri, $xml, User $user, Profile $target=null)
     {
         if (empty($endpoint_uri)) {
             common_debug('No endpoint URI for Salmon post to '.$user->getUri());
@@ -60,7 +60,8 @@ class Salmon
             return false;
         }
 
-        if (Event::handle('SalmonSlap', array($magic_env))) {
+        // $target is so far only used in Diaspora, so it can be null
+        if (Event::handle('SalmonSlap', array($endpoint_uri, $magic_env, $target))) {
             return false;
             //throw new ServerException('Could not distribute salmon slap as no plugin completed the event.');
         }