public function createMagicEnv($text, $actor) { $magic_env = new MagicEnvelope(); $user = User::staticGet('id', $actor->id); if ($user->id) { // Use local key $magickey = Magicsig::staticGet('user_id', $user->id); if (!$magickey) { // No keypair yet, let's generate one. $magickey = new Magicsig(); $magickey->generate($user->id); } } else { throw new Exception("Salmon invalid actor for signing"); } try { $env = $magic_env->signMessage($text, 'application/atom+xml', $magickey->toString()); } catch (Exception $e) { return $text; } return $magic_env->toXML($env); }
/** * Encode the given string as a signed MagicEnvelope XML document, * using the keypair for the given local user profile. We can of * course not sign a remote profile's slap, since we don't have the * private key. * * Side effects: will create and store a keypair on-demand if one * hasn't already been generated for this user. This can be very slow * on some systems. * * @param string $text XML fragment to sign, assumed to be Atom * @param User $user User who cryptographically signs $text * * @return MagicEnvelope object complete with signature * * @throws Exception on bad profile input or key generation problems */ public static function signAsUser($text, User $user) { // Find already stored key $magicsig = Magicsig::getKV('user_id', $user->id); if (!$magicsig instanceof Magicsig) { $magicsig = Magicsig::generate($user); } assert($magicsig instanceof Magicsig); assert($magicsig->privateKey instanceof Crypt_RSA); $magic_env = new MagicEnvelope(); $magic_env->signMessage($text, 'application/atom+xml', $magicsig); return $magic_env; }
/** * Encode the given string as a signed MagicEnvelope XML document, * using the keypair for the given local user profile. We can of * course not sign a remote profile's slap, since we don't have the * private key. * * Side effects: will create and store a keypair on-demand if one * hasn't already been generated for this user. This can be very slow * on some systems. * * @param string $text XML fragment to sign, assumed to be Atom * @param User $user User who cryptographically signs $text * * @return MagicEnvelope object complete with signature * * @throws Exception on bad profile input or key generation problems */ public static function signAsUser($text, User $user) { $magic_env = new MagicEnvelope(null, $user->getProfile()); $magic_env->signMessage($text, 'application/atom+xml'); return $magic_env; }
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 <diaspora> 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 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”. */ 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); /** * 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()); //FIXME: This means we don't actually put an <atom:entry> 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; }