From 51d1535f155c1178d8d17a40f06b6d9f7df9ff98 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 5 Jan 2011 14:05:59 -0800 Subject: [PATCH 1/6] Added doc comments on Salmon magicsig-related stuff to help in figuring out what's going on --- plugins/OStatus/classes/Magicsig.php | 111 +++++++++++++++++++- plugins/OStatus/classes/Ostatus_profile.php | 1 + plugins/OStatus/lib/magicenvelope.php | 72 +++++++++++++ plugins/OStatus/lib/salmon.php | 34 +++++- 4 files changed, 215 insertions(+), 3 deletions(-) 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(); From e25c34a2b60c4d6050896e51c63f1ba52dfbc45f Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 5 Jan 2011 14:27:53 -0800 Subject: [PATCH 2/6] Salmon slap / magicsig test script Given a notice in the local system, we package it up as an Atom entry and MagicSig it up. We run the magicenv verification on it locally to make sure our own functions can decode it. Optionally with --verify we can send to Tuomas Koski's verification test service (not sure if this is working 100%) If given --slap= with a target Salmon endpoint, we'll sent it on and see if it liked it. (Note that StatusNet will reject if there's not a relevant mention, but will report acceptance for dupes so you can use a message that's already been delivered as a test.) --- plugins/OStatus/tests/slap.php | 92 ++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 plugins/OStatus/tests/slap.php diff --git a/plugins/OStatus/tests/slap.php b/plugins/OStatus/tests/slap.php new file mode 100644 index 0000000000..b5f9d3e073 --- /dev/null +++ b/plugins/OStatus/tests/slap.php @@ -0,0 +1,92 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..')); + +$longoptions = array('verify', 'slap=', 'notice='); + +$helptext = << send signed Salmon slap to the destination endpoint + + +END_OF_HELP; + +require_once INSTALLDIR.'/scripts/commandline.inc'; + +if (!have_option('--notice')) { + print "$helptext"; + exit(1); +} + +$notice_id = get_option_value('--notice'); + +$notice = Notice::staticGet('id', $notice_id); +$profile = $notice->getProfile(); +$entry = $notice->asAtomEntry(true); + +echo "== Original entry ==\n\n"; +print $entry; +print "\n\n"; + +$salmon = new Salmon(); +$envelope = $salmon->createMagicEnv($entry, $profile); + +echo "== Signed envelope ==\n\n"; +print $envelope; +print "\n\n"; + +echo "== Testing local verification ==\n\n"; +$ok = $salmon->verifyMagicEnv($envelope); +if ($ok) { + print "OK\n\n"; +} else { + print "FAIL\n\n"; +} + +if (have_option('--verify')) { + $url = 'http://www.madebymonsieur.com/ostatus_discovery/magic_env/validate/'; + echo "== Testing remote verification ==\n\n"; + print "Sending for verification to $url ...\n"; + + $client = new HTTPClient(); + $response = $client->post($url, array(), array('magic_env' => $envelope)); + + print $response->getStatus() . "\n\n"; + print $response->getBody() . "\n\n"; +} + +if (have_option('--slap')) { + $url = get_option_value('--slap'); + echo "== Remote salmon slap ==\n\n"; + print "Sending signed Salmon slap to $url ...\n"; + + $ok = $salmon->post($url, $entry, $profile); + if ($ok) { + print "OK\n\n"; + } else { + print "FAIL\n\n"; + } +} From 946a4ac17b5152119e8618d3339777159b524d53 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 5 Jan 2011 23:26:09 +0000 Subject: [PATCH 3/6] Add test cases for internal change in Salmon signing; fix for the new code. Updated sig passes Tuomas's verifier, which is a good sign --- plugins/OStatus/lib/magicenvelope.php | 46 ++++++++++++++-- plugins/OStatus/tests/MagicEnvelopeTest.php | 60 +++++++++++++++++++++ 2 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 plugins/OStatus/tests/MagicEnvelopeTest.php diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index 6d3956cb9b..384506280d 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -80,6 +80,20 @@ class MagicEnvelope throw new Exception(_m('Unable to locate signer public key.')); } + /** + * The current MagicEnvelope spec as used in StatusNet 0.9.7 and later + * includes both the original data and some signing metadata fields as + * the input plaintext for the signature hash. + * + * @param array $env + * @return string + */ + public function signingText($env) { + return implode('.', array($env['data'], // this field is pre-base64'd + Magicsig::base64_url_encode($env['data_type']), + Magicsig::base64_url_encode($env['encoding']), + Magicsig::base64_url_encode($env['alg']))); + } /** * @@ -93,14 +107,17 @@ class MagicEnvelope { $signature_alg = Magicsig::fromString($keypair); $armored_text = Magicsig::base64_url_encode($text); - - return array( + $env = array( 'data' => $armored_text, 'encoding' => MagicEnvelope::ENCODING, 'data_type' => $mimetype, - 'sig' => $signature_alg->sign($armored_text), + 'sig' => '', 'alg' => $signature_alg->getName() ); + + $env['sig'] = $signature_alg->sign($this->signingText($env)); + + return $env; } /** @@ -239,7 +256,7 @@ class MagicEnvelope return false; } - return $verifier->verify($env['data'], $env['sig']); + return $verifier->verify($this->signingText($env), $env['sig']); } /** @@ -290,3 +307,24 @@ class MagicEnvelope ); } } + +/** + * Variant of MagicEnvelope using the earlier signature form listed in the MagicEnvelope + * spec in early 2010; this was used in StatusNet up through 0.9.6, so for backwards compatiblity + * we still need to accept and sometimes send this format. + */ +class MagicEnvelopeCompat extends MagicEnvelope { + + /** + * StatusNet through 0.9.6 used an earlier version of the MagicEnvelope spec + * which used only the input data, without the additional fields, as the plaintext + * for signing. + * + * @param array $env + * @return string + */ + public function signingText($env) { + return $env['data']; + } +} + diff --git a/plugins/OStatus/tests/MagicEnvelopeTest.php b/plugins/OStatus/tests/MagicEnvelopeTest.php new file mode 100644 index 0000000000..5ee0362d28 --- /dev/null +++ b/plugins/OStatus/tests/MagicEnvelopeTest.php @@ -0,0 +1,60 @@ +signingText($env); + + $this->assertEquals($expected, $text, "'$text' should be '$expected'"); + } + + static public function provider() + { + return array( + array( + // Sample case given in spec: + // http://salmon-protocol.googlecode.com/svn/trunk/draft-panzer-magicsig-00.html#signing + array( + 'data' => 'Tm90IHJlYWxseSBBdG9t', + 'data_type' => 'application/atom+xml', + 'encoding' => 'base64url', + 'alg' => 'RSA-SHA256' + ), + 'Tm90IHJlYWxseSBBdG9t.YXBwbGljYXRpb24vYXRvbSt4bWw=.YmFzZTY0dXJs.UlNBLVNIQTI1Ng==' + ) + ); + } + + + /** + * Test that MagicEnvelope builds the correct plaintext for signing. + * @dataProvider provider + */ + public function testSignatureTextCompat($env, $expected) + { + // Our old code didn't add the extra fields, just used the armored text. + $alt = $env['data']; + + $magic = new MagicEnvelopeCompat; + $text = $magic->signingText($env); + + $this->assertEquals($alt, $text, "'$text' should be '$alt'"); + } + +} From f5650806cc0556d93ada1b43b16608ea3695c76a Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 5 Jan 2011 23:27:17 +0000 Subject: [PATCH 4/6] Switch autoloader from '__autoload' magic function name to registering our function with spl_autoload_register(); fixes compat problem with PHPUnit 3.5+ which seems to break the old __autoload --- lib/common.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/common.php b/lib/common.php index 6138200e49..b072a49699 100644 --- a/lib/common.php +++ b/lib/common.php @@ -95,7 +95,11 @@ function _have_config() return StatusNet::haveConfig(); } -function __autoload($cls) +/** + * Wrapper for class autoloaders. + * This used to be the special function name __autoload(), but that causes bugs with PHPUnit 3.5+ + */ +function autoload_sn($cls) { if (file_exists(INSTALLDIR.'/classes/' . $cls . '.php')) { require_once(INSTALLDIR.'/classes/' . $cls . '.php'); @@ -111,6 +115,8 @@ function __autoload($cls) } } +spl_autoload_register('autoload_sn'); + // XXX: how many of these could be auto-loaded on use? // XXX: note that these files should not use config options // at compile time since DB config options are not yet loaded. From 437ac120b07542952c30c21ec93f8ebecda012a3 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 5 Jan 2011 23:54:16 +0000 Subject: [PATCH 5/6] Outgoing Salmon slaps now use the corrected signature format; if the first hit is rejected with an HTTP error, we try again with the old format. (This is not 100% ideal; possibly should try to distinguish between server errors and rejections, etc.) --- plugins/OStatus/lib/salmon.php | 54 +++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/plugins/OStatus/lib/salmon.php b/plugins/OStatus/lib/salmon.php index 6137f7bee5..5535049203 100644 --- a/plugins/OStatus/lib/salmon.php +++ b/plugins/OStatus/lib/salmon.php @@ -52,29 +52,35 @@ class Salmon return false; } - try { - $xml = $this->createMagicEnv($xml, $actor); - } catch (Exception $e) { - common_log(LOG_ERR, "Salmon unable to sign: " . $e->getMessage()); - return false; - } + $classes = array('MagicEnvelope', 'MagicEnvelopeCompat'); + foreach ($classes as $class) { + try { + $envelope = $this->createMagicEnv($xml, $actor, $class); + } catch (Exception $e) { + common_log(LOG_ERR, "Salmon unable to sign: " . $e->getMessage()); + return false; + } + + $headers = array('Content-Type: application/magic-envelope+xml'); + + try { + $client = new HTTPClient(); + $client->setBody($envelope); + $response = $client->post($endpoint_uri, $headers); + } catch (HTTP_Request2_Exception $e) { + common_log(LOG_ERR, "Salmon ($class) post to $endpoint_uri failed: " . $e->getMessage()); + continue; + } + if ($response->getStatus() != 200) { + common_log(LOG_ERR, "Salmon ($class) at $endpoint_uri returned status " . + $response->getStatus() . ': ' . $response->getBody()); + continue; + } - $headers = array('Content-Type: application/magic-envelope+xml'); - - try { - $client = new HTTPClient(); - $client->setBody($xml); - $response = $client->post($endpoint_uri, $headers); - } catch (HTTP_Request2_Exception $e) { - common_log(LOG_ERR, "Salmon post to $endpoint_uri failed: " . $e->getMessage()); - return false; + // Success! + return true; } - if ($response->getStatus() != 200) { - common_log(LOG_ERR, "Salmon at $endpoint_uri returned status " . - $response->getStatus() . ': ' . $response->getBody()); - return false; - } - return true; + return false; } /** @@ -87,14 +93,16 @@ class Salmon * * @param string $text XML fragment to sign, assumed to be Atom * @param Profile $actor Profile of a local user to use as signer + * @param string $class to override the magic envelope signature version, pass a MagicEnvelope subclass here + * * @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, $class='MagicEnvelope') { - $magic_env = new MagicEnvelope(); + $magic_env = new $class(); $user = User::staticGet('id', $actor->id); if ($user->id) { From 1d15145993a00d1db1057dacf71f3783cd16c119 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 6 Jan 2011 00:01:42 +0000 Subject: [PATCH 6/6] Salmon signature checks on incoming slaps now check both old and new signature formats. --- plugins/OStatus/lib/salmon.php | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/plugins/OStatus/lib/salmon.php b/plugins/OStatus/lib/salmon.php index 5535049203..2f5772a844 100644 --- a/plugins/OStatus/lib/salmon.php +++ b/plugins/OStatus/lib/salmon.php @@ -52,8 +52,7 @@ class Salmon return false; } - $classes = array('MagicEnvelope', 'MagicEnvelopeCompat'); - foreach ($classes as $class) { + foreach ($this->formatClasses() as $class) { try { $envelope = $this->createMagicEnv($xml, $actor, $class); } catch (Exception $e) { @@ -83,6 +82,15 @@ class Salmon return false; } + /** + * List the magic envelope signature class variants in the order we try them. + * Multiples are needed for backwards-compat with StatusNet prior to 0.9.7, + * which used a draft version of the magic envelope spec. + */ + protected function formatClasses() { + return array('MagicEnvelope', 'MagicEnvelopeCompat'); + } + /** * Encode the given string as a signed MagicEnvelope XML document, * using the keypair for the given local user profile. @@ -129,6 +137,7 @@ class Salmon /** * Check if the given magic envelope is well-formed and correctly signed. * Needs to have network access to fetch public keys over the web. + * Both current and back-compat signature formats will be checked. * * Side effects: exceptions and caching updates may occur during network * fetches. @@ -141,10 +150,16 @@ class Salmon */ public function verifyMagicEnv($text) { - $magic_env = new MagicEnvelope(); + foreach ($this->formatClasses() as $class) { + $magic_env = new $class(); - $env = $magic_env->parse($text); + $env = $magic_env->parse($text); - return $magic_env->verify($env); + if ($magic_env->verify($env)) { + return true; + } + } + + return false; } }