Added doc comments on Salmon magicsig-related stuff to help in figuring out what's going on

This commit is contained in:
Brion Vibber 2011-01-05 14:05:59 -08:00
parent 021fa25e8d
commit 51d1535f15
4 changed files with 215 additions and 3 deletions

View File

@ -39,11 +39,41 @@ class Magicsig extends Memcached_DataObject
public $__table = 'magicsig'; public $__table = 'magicsig';
/**
* Key to user.id/profile.id for the local user whose key we're storing.
*
* @var int
*/
public $user_id; 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; public $keypair;
/**
* Crypto algorithm used for this key; currently only RSA-SHA256 is supported.
*
* @var string
*/
public $alg; public $alg;
/**
* Public RSA key; gets serialized in/out via $this->keypair string.
*
* @var Crypt_RSA
*/
public $publicKey; public $publicKey;
/**
* PrivateRSA key; gets serialized in/out via $this->keypair string.
*
* @var Crypt_RSA
*/
public $privateKey; public $privateKey;
public function __construct($alg = 'RSA-SHA256') public function __construct($alg = 'RSA-SHA256')
@ -51,6 +81,13 @@ class Magicsig extends Memcached_DataObject
$this->alg = $alg; $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) public /*static*/ function staticGet($k, $v=null)
{ {
$obj = parent::staticGet(__CLASS__, $k, $v); $obj = parent::staticGet(__CLASS__, $k, $v);
@ -103,6 +140,14 @@ class Magicsig extends Memcached_DataObject
return array(false, false, false); 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() function insert()
{ {
$this->keypair = $this->toString(); $this->keypair = $this->toString();
@ -110,6 +155,14 @@ class Magicsig extends Memcached_DataObject
return parent::insert(); 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) public function generate($user_id)
{ {
$rsa = new Crypt_RSA(); $rsa = new Crypt_RSA();
@ -128,6 +181,12 @@ class Magicsig extends Memcached_DataObject
$this->insert(); $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) public function toString($full_pair = true)
{ {
$mod = Magicsig::base64_url_encode($this->publicKey->modulus->toBytes()); $mod = Magicsig::base64_url_encode($this->publicKey->modulus->toBytes());
@ -140,6 +199,13 @@ class Magicsig extends Memcached_DataObject
return 'RSA.' . $mod . '.' . $exp . $private_exp; 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) public static function fromString($text)
{ {
$magic_sig = new Magicsig(); $magic_sig = new Magicsig();
@ -168,6 +234,14 @@ class Magicsig extends Memcached_DataObject
return $magic_sig; 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') public function loadKey($mod, $exp, $type = 'public')
{ {
common_log(LOG_DEBUG, "Adding ".$type." key: (".$mod .', '. $exp .")"); 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() public function getName()
{ {
return $this->alg; 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() public function getHash()
{ {
switch ($this->alg) { 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) public function sign($bytes)
{ {
$sig = $this->privateKey->sign($bytes); $sig = $this->privateKey->sign($bytes);
return Magicsig::base64_url_encode($sig); 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) public function verify($signed_bytes, $signature)
{ {
$signature = Magicsig::base64_url_decode($signature); $signature = Magicsig::base64_url_decode($signature);
return $this->publicKey->verify($signed_bytes, $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) public static function base64_url_encode($input)
{ {
return strtr(base64_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) public static function base64_url_decode($input)
{ {
return base64_decode(strtr($input, '-_', '+/')); return base64_decode(strtr($input, '-_', '+/'));

View File

@ -331,6 +331,7 @@ class Ostatus_profile extends Memcached_DataObject
* an acceptable response from the remote site. * an acceptable response from the remote site.
* *
* @param mixed $entry XML string, Notice, or Activity * @param mixed $entry XML string, Notice, or Activity
* @param Profile $actor
* @return boolean success * @return boolean success
*/ */
public function notifyActivity($entry, $actor) public function notifyActivity($entry, $actor)

View File

@ -81,6 +81,14 @@ class MagicEnvelope
} }
/**
*
* @param <type> $text
* @param <type> $mimetype
* @param <type> $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) public function signMessage($text, $mimetype, $keypair)
{ {
$signature_alg = Magicsig::fromString($keypair); $signature_alg = Magicsig::fromString($keypair);
@ -95,6 +103,13 @@ class MagicEnvelope
); );
} }
/**
* Create an <me:env> 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) { public function toXML($env) {
$xs = new XMLStringer(); $xs = new XMLStringer();
$xs->startXML(); $xs->startXML();
@ -110,6 +125,16 @@ class MagicEnvelope
return $string; return $string;
} }
/**
* Extract the contained XML payload, and insert a copy of the envelope
* signature data as an <me:provenance> 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) public function unfold($env)
{ {
$dom = new DOMDocument(); $dom = new DOMDocument();
@ -136,6 +161,14 @@ class MagicEnvelope
return $dom->saveXML(); 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) { public function getAuthor($text) {
$doc = new DOMDocument(); $doc = new DOMDocument();
if (!$doc->loadXML($text)) { 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) public function checkAuthor($text, $signer_uri)
{ {
return ($this->getAuthor($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) public function verify($env)
{ {
if ($env['alg'] != 'RSA-SHA256') { if ($env['alg'] != 'RSA-SHA256') {
@ -190,12 +242,32 @@ class MagicEnvelope
return $verifier->verify($env['data'], $env['sig']); return $verifier->verify($env['data'], $env['sig']);
} }
/**
* Extract envelope data from an XML document containing an <me:env> or <me:provenance> 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) public function parse($text)
{ {
$dom = DOMDocument::loadXML($text); $dom = DOMDocument::loadXML($text);
return $this->fromDom($dom); return $this->fromDom($dom);
} }
/**
* Extract envelope data from an XML document containing an <me:env> or <me:provenance> 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) public function fromDom($dom)
{ {
$env_element = $dom->getElementsByTagNameNS(MagicEnvelope::NS, 'env')->item(0); $env_element = $dom->getElementsByTagNameNS(MagicEnvelope::NS, 'env')->item(0);

View File

@ -38,10 +38,12 @@ class Salmon
/** /**
* Sign and post the given Atom entry as a Salmon message. * 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 $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 * @return boolean success
*/ */
public function post($endpoint_uri, $xml, $actor) public function post($endpoint_uri, $xml, $actor)
@ -75,6 +77,21 @@ class Salmon
return true; 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) public function createMagicEnv($text, $actor)
{ {
$magic_env = new MagicEnvelope(); $magic_env = new MagicEnvelope();
@ -101,6 +118,19 @@ class Salmon
return $magic_env->toXML($env); 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) public function verifyMagicEnv($text)
{ {
$magic_env = new MagicEnvelope(); $magic_env = new MagicEnvelope();