X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=plugins%2FDiaspora%2FDiasporaPlugin.php;h=c054d3b767658cfd32cd5629e428569bb402bb62;hb=4211206e3be06baa6c63af8c9fc819db3020d559;hp=66a9759f8787078d984914a803c29a9653c9ec74;hpb=d4fc064e4420d74dbff0587ba2b05d774e85a51b;p=quix0rs-gnu-social.git diff --git a/plugins/Diaspora/DiasporaPlugin.php b/plugins/Diaspora/DiasporaPlugin.php index 66a9759f87..c054d3b767 100644 --- a/plugins/Diaspora/DiasporaPlugin.php +++ b/plugins/Diaspora/DiasporaPlugin.php @@ -30,6 +30,9 @@ if (!defined('GNUSOCIAL')) { exit(1); } * @maintainer Mikael Nordfeldth */ +// Depends on OStatus of course. +addPlugin('OStatus'); + class DiasporaPlugin extends Plugin { const REL_SEED_LOCATION = 'http://joindiaspora.com/seed_location'; @@ -40,7 +43,7 @@ class DiasporaPlugin extends Plugin { // So far we've only handled RSA keys, but it can change in the future, // so be prepared. And remember to change the statically assigned type attribute below! - assert($magicsig->publicKey instanceof Crypt_RSA); + assert($magicsig->publicKey instanceof \phpseclib\Crypt\RSA); $xrd->links[] = new XML_XRD_Element_Link(self::REL_PUBLIC_KEY, base64_encode($magicsig->exportPublicKey()), 'RSA'); @@ -50,10 +53,33 @@ class DiasporaPlugin extends Plugin strtolower($magicsig->toFingerprint())); } + public function onMagicsigPublicKeyFromXRD(XML_XRD $xrd, &$pubkey) + { + // See if we have a Diaspora public key in the XRD response + $link = $xrd->get(self::REL_PUBLIC_KEY, 'RSA'); + if (!is_null($link)) { + // If we do, decode it so we have the PKCS1 format (starts with -----BEGIN PUBLIC KEY-----) + $pkcs1 = base64_decode($link->href); + $magicsig = new Magicsig(Magicsig::DEFAULT_SIGALG); // Diaspora uses RSA-SHA256 (we do too) + try { + // Try to load the public key so we can get it in the standard Magic signature format + $magicsig->loadPublicKeyPKCS1($pkcs1); + // We found it and will now store it in $pubkey in a proper format! + // This is how it would be found in a well implemented XRD according to the standard. + $pubkey = 'data:application/magic-public-key,'.$magicsig->toString(); + common_debug('magic-public-key found in diaspora-public-key: '.$pubkey); + return false; + } catch (ServerException $e) { + common_log(LOG_WARNING, $e->getMessage()); + } + } + return true; + } + public function onPluginVersion(array &$versions) { $versions[] = array('name' => 'Diaspora', - 'version' => '0.1', + 'version' => '0.2', 'author' => 'Mikael Nordfeldth', 'homepage' => 'https://gnu.io/social', // TRANS: Plugin description. @@ -62,4 +88,174 @@ 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 + * http://www.rubydoc.info/github/Raven24/diaspora-federation/master/DiasporaFederation/Salmon/EncryptedSlap + * + * Constructing the encryption header + */ + + // For some reason diaspora wants the salmon slap in a header. + $xs->elementStart('diaspora', array('xmlns'=>'https://joindiaspora.com/protocol')); + + /** + * 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 \phpseclib\Crypt\AES(\phpseclib\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: + * + * ((base64-encoded inner iv)) + * ((base64-encoded inner key)) + * + * Alice Exampleman + * acct:user@sender.example + * + * + */ + $decrypted_header = sprintf('%1$s%2$s%3$s', + 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 \phpseclib\Crypt\AES(\phpseclib\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 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, \phpseclib\Crypt\RSA::PADDING_PKCS1); + + /** + * 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”. + */ + common_debug('Diaspora creating "outer aes key bundle", will require magic-public-key'); + $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, \phpseclib\Crypt\RSA::PADDING_PKCS1); + + /** + * 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: + * ((base64-encoded encrypted header json object)) + */ + $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(), \phpseclib\Crypt\RSA::PADDING_PKCS1); + //FIXME: This means we don't actually put an in the payload, + // since Diaspora has its own update method! Silly me. Read up on: + // https://wiki.diasporafoundation.org/Federation_Message_Semantics + $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'); + + $xs->elementEnd('entry'); + + return false; + } + + public function onSalmonSlap($endpoint_uri, MagicEnvelope $magic_env, Profile $target=null) + { + try { + $envxml = $magic_env->toXML($target, 'diaspora'); + } catch (Exception $e) { + common_log(LOG_ERR, sprintf('Could not generate Magic Envelope XML (diaspora flavour) for profile id=='.$target->getID().': '.$e->getMessage())); + return false; + } + + // 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 (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', + $magic_env->getActor()->getID(), $endpoint_uri, $response->getStatus(), $response->getBody())); + return true; + } + + // Success! + return false; + } }