Added doc comments on Salmon magicsig-related stuff to help in figuring out what's going on
This commit is contained in:
parent
021fa25e8d
commit
51d1535f15
|
@ -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, '-_', '+/'));
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user