diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php index e057deb144..31d061e6a0 100644 --- a/plugins/OStatus/classes/Magicsig.php +++ b/plugins/OStatus/classes/Magicsig.php @@ -39,11 +39,41 @@ class Magicsig extends Memcached_DataObject public $__table = 'magicsig'; + /** + * Key to user.id/profile.id for the local user whose key we're storing. + * + * @var int + */ public $user_id; + + /** + * Flattened string representation of the key pair; callers should + * usually use $this->publicKey and $this->privateKey directly, + * which hold live Crypt_RSA key objects. + * + * @var string + */ public $keypair; + + /** + * Crypto algorithm used for this key; currently only RSA-SHA256 is supported. + * + * @var string + */ public $alg; + /** + * Public RSA key; gets serialized in/out via $this->keypair string. + * + * @var Crypt_RSA + */ public $publicKey; + + /** + * PrivateRSA key; gets serialized in/out via $this->keypair string. + * + * @var Crypt_RSA + */ public $privateKey; public function __construct($alg = 'RSA-SHA256') @@ -51,6 +81,13 @@ class Magicsig extends Memcached_DataObject $this->alg = $alg; } + /** + * Fetch a Magicsig object from the cache or database on a field match. + * + * @param string $k + * @param mixed $v + * @return Magicsig + */ public /*static*/ function staticGet($k, $v=null) { $obj = parent::staticGet(__CLASS__, $k, $v); @@ -103,6 +140,14 @@ class Magicsig extends Memcached_DataObject return array(false, false, false); } + /** + * Save this keypair into the database. + * + * Overloads default insert behavior to encode the live key objects + * as a flat string for storage. + * + * @return mixed + */ function insert() { $this->keypair = $this->toString(); @@ -110,6 +155,14 @@ class Magicsig extends Memcached_DataObject return parent::insert(); } + /** + * Generate a new keypair for a local user and store in the database. + * + * Warning: this can be very slow on systems without the GMP module. + * Runtimes of 20-30 seconds are not unheard-of. + * + * @param int $user_id id of local user we're creating a key for + */ public function generate($user_id) { $rsa = new Crypt_RSA(); @@ -128,6 +181,12 @@ class Magicsig extends Memcached_DataObject $this->insert(); } + /** + * Encode the keypair or public key as a string. + * + * @param boolean $full_pair set to false to leave out the private key. + * @return string + */ public function toString($full_pair = true) { $mod = Magicsig::base64_url_encode($this->publicKey->modulus->toBytes()); @@ -140,6 +199,13 @@ class Magicsig extends Memcached_DataObject return 'RSA.' . $mod . '.' . $exp . $private_exp; } + /** + * Decode a string representation of an RSA public key or keypair + * as a Magicsig object which can be used to sign or verify. + * + * @param string $text + * @return Magicsig + */ public static function fromString($text) { $magic_sig = new Magicsig(); @@ -168,6 +234,14 @@ class Magicsig extends Memcached_DataObject return $magic_sig; } + /** + * Fill out $this->privateKey or $this->publicKey with a Crypt_RSA object + * representing the give key (as mod/exponent pair). + * + * @param string $mod base64-encoded + * @param string $exp base64-encoded exponent + * @param string $type one of 'public' or 'private' + */ public function loadKey($mod, $exp, $type = 'public') { common_log(LOG_DEBUG, "Adding ".$type." key: (".$mod .', '. $exp .")"); @@ -186,11 +260,22 @@ class Magicsig extends Memcached_DataObject } } + /** + * Returns the name of the crypto algorithm used for this key. + * + * @return string + */ public function getName() { return $this->alg; } + /** + * Returns the name of a hash function to use for signing with this key. + * + * @return string + * @fixme is this used? doesn't seem to be called by name. + */ public function getHash() { switch ($this->alg) { @@ -200,24 +285,48 @@ class Magicsig extends Memcached_DataObject } } + /** + * Generate base64-encoded signature for the given byte string + * using our private key. + * + * @param string $bytes as raw byte string + * @return string base64-encoded signature + */ public function sign($bytes) { $sig = $this->privateKey->sign($bytes); return Magicsig::base64_url_encode($sig); } + /** + * + * @param string $signed_bytes as raw byte string + * @param string $signature as base64 + * @return boolean + */ public function verify($signed_bytes, $signature) { $signature = Magicsig::base64_url_decode($signature); return $this->publicKey->verify($signed_bytes, $signature); } - + /** + * URL-encoding-friendly base64 variant encoding. + * + * @param string $input + * @return string + */ public static function base64_url_encode($input) { return strtr(base64_encode($input), '+/', '-_'); } + /** + * URL-encoding-friendly base64 variant decoding. + * + * @param string $input + * @return string + */ public static function base64_url_decode($input) { return base64_decode(strtr($input, '-_', '+/')); diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 9c0f014fc6..06e42187d4 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -331,6 +331,7 @@ class Ostatus_profile extends Memcached_DataObject * an acceptable response from the remote site. * * @param mixed $entry XML string, Notice, or Activity + * @param Profile $actor * @return boolean success */ public function notifyActivity($entry, $actor) diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index 03e6f7c665..6d3956cb9b 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -81,6 +81,14 @@ class MagicEnvelope } + /** + * + * @param $text + * @param $mimetype + * @param $keypair + * @return array: associative array of envelope properties + * @fixme it might be easier to work with storing envelope data these in the object instead of passing arrays around + */ public function signMessage($text, $mimetype, $keypair) { $signature_alg = Magicsig::fromString($keypair); @@ -95,6 +103,13 @@ class MagicEnvelope ); } + /** + * Create an XML representation of the envelope. + * + * @param array $env associative array with envelope data + * @return string representation of XML document + * @fixme it might be easier to work with storing envelope data these in the object instead of passing arrays around + */ public function toXML($env) { $xs = new XMLStringer(); $xs->startXML(); @@ -110,6 +125,16 @@ class MagicEnvelope return $string; } + /** + * Extract the contained XML payload, and insert a copy of the envelope + * signature data as an section. + * + * @param array $env associative array with envelope data + * @return string representation of modified XML document + * + * @fixme in case of XML parsing errors, this will spew to the error log or output + * @fixme it might be easier to work with storing envelope data these in the object instead of passing arrays around + */ public function unfold($env) { $dom = new DOMDocument(); @@ -136,6 +161,14 @@ class MagicEnvelope return $dom->saveXML(); } + /** + * Find the author URI referenced in the given Atom entry. + * + * @param string $text string containing Atom entry XML + * @return mixed URI string or false if XML parsing fails, or null if no author URI can be found + * + * @fixme XML parsing failures will spew to error logs/output + */ public function getAuthor($text) { $doc = new DOMDocument(); if (!$doc->loadXML($text)) { @@ -153,11 +186,30 @@ class MagicEnvelope } } + /** + * Check if the author in the Atom entry fragment claims to match + * the given identifier URI. + * + * @param string $text string containing Atom entry XML + * @param string $signer_uri + * @return boolean + */ public function checkAuthor($text, $signer_uri) { return ($this->getAuthor($text) == $signer_uri); } + /** + * Attempt to verify cryptographic signing for parsed envelope data. + * Requires network access to retrieve public key referenced by the envelope signer. + * + * Details of failure conditions are dumped to output log and not exposed to caller. + * + * @param array $env array representation of magic envelope data, as returned from MagicEnvelope::parse() + * @return boolean + * + * @fixme it might be easier to work with storing envelope data these in the object instead of passing arrays around + */ public function verify($env) { if ($env['alg'] != 'RSA-SHA256') { @@ -190,12 +242,32 @@ class MagicEnvelope return $verifier->verify($env['data'], $env['sig']); } + /** + * Extract envelope data from an XML document containing an or element. + * + * @param string XML source + * @return mixed associative array of envelope data, or false on unrecognized input + * + * @fixme it might be easier to work with storing envelope data these in the object instead of passing arrays around + * @fixme will spew errors to logs or output in case of XML parse errors + * @fixme may give fatal errors if some elements are missing or invalid XML + * @fixme calling DOMDocument::loadXML statically triggers warnings in strict mode + */ public function parse($text) { $dom = DOMDocument::loadXML($text); return $this->fromDom($dom); } + /** + * Extract envelope data from an XML document containing an or element. + * + * @param DOMDocument $dom + * @return mixed associative array of envelope data, or false on unrecognized input + * + * @fixme it might be easier to work with storing envelope data these in the object instead of passing arrays around + * @fixme may give fatal errors if some elements are missing + */ public function fromDom($dom) { $env_element = $dom->getElementsByTagNameNS(MagicEnvelope::NS, 'env')->item(0); diff --git a/plugins/OStatus/lib/salmon.php b/plugins/OStatus/lib/salmon.php index 963da65084..6137f7bee5 100644 --- a/plugins/OStatus/lib/salmon.php +++ b/plugins/OStatus/lib/salmon.php @@ -38,10 +38,12 @@ class Salmon /** * Sign and post the given Atom entry as a Salmon message. * - * @fixme pass through the actor for signing? + * Side effects: may generate a keypair on-demand for the given user, + * which can be very slow on some systems. * * @param string $endpoint_uri - * @param string $xml + * @param string $xml string representation of payload + * @param Profile $actor local user profile whose keys to sign with * @return boolean success */ public function post($endpoint_uri, $xml, $actor) @@ -75,6 +77,21 @@ class Salmon return true; } + /** + * Encode the given string as a signed MagicEnvelope XML document, + * using the keypair for the given local user profile. + * + * 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 Profile $actor Profile of a local user to use as signer + * @return string XML string representation of magic envelope + * + * @throws Exception on bad profile input or key generation problems + * @fixme if signing fails, this seems to return the original text without warning. Is there a reason for this? + */ public function createMagicEnv($text, $actor) { $magic_env = new MagicEnvelope(); @@ -101,6 +118,19 @@ class Salmon return $magic_env->toXML($env); } + /** + * Check if the given magic envelope is well-formed and correctly signed. + * Needs to have network access to fetch public keys over the web. + * + * Side effects: exceptions and caching updates may occur during network + * fetches. + * + * @param string $text XML fragment of magic envelope + * @return boolean + * + * @throws Exception on bad profile input or key generation problems + * @fixme could hit fatal errors or spew output on invalid XML + */ public function verifyMagicEnv($text) { $magic_env = new MagicEnvelope();