From 0e217a42c5b53648e05f37dd08a66c01b7ff2022 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Wed, 6 Jul 2016 11:37:13 +0200 Subject: [PATCH] introduce new zerobincompatibility option, replacing the base64 one, if it is enabled, delete tokens use sha256; added per paste salt with server salt fallback; this resolves the points 2.2 & 2.9 in #103 --- cfg/conf.ini.sample | 11 ++++---- lib/configuration.php | 2 +- lib/model/paste.php | 16 +++++++++-- lib/zerobin.php | 4 +-- tst/bootstrap.php | 3 ++ tst/configuration.php | 2 +- tst/jsonApi.php | 22 +++++++++------ tst/zerobin.php | 65 +++++++++++++++++++++++++++++++------------ 8 files changed, 87 insertions(+), 38 deletions(-) diff --git a/cfg/conf.ini.sample b/cfg/conf.ini.sample index 01433a2..31e74eb 100644 --- a/cfg/conf.ini.sample +++ b/cfg/conf.ini.sample @@ -39,10 +39,6 @@ template = "bootstrap" ; (optional) notice to display ; notice = "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service." -; base64.js library version, defaults to 2.1.9 -; use "1.7" if you are upgrading from a ZeroBin Alpha 0.19 installation -base64version = "2.1.9" - ; by default ZeroBin will guess the visitors language based on the browsers ; settings. Optionally you can enable the language selection menu, which uses ; a session cookie to store the choice until the browser is closed. @@ -57,6 +53,11 @@ languageselection = false ; the pastes encryption key ; urlshortener = "https://shortener.example.com/api?link=" +; stay compatible with ZeroBin Alpha 0.19, less secure +; if enabled will use base64.js version 1.7 instead of 2.1.9 and sha1 instead of +; sha256 in HMAC for the deletion token +zerobincompatibility = false + [expire] ; expire value that is selected per default ; make sure the value exists in [expire_options] @@ -121,4 +122,4 @@ dir = PATH "data" ;dsn = "sqlite:" PATH "data/db.sq3" ;usr = null ;pwd = null -;opt[12] = true ; PDO::ATTR_PERSISTENT \ No newline at end of file +;opt[12] = true ; PDO::ATTR_PERSISTENT diff --git a/lib/configuration.php b/lib/configuration.php index 76ddb55..713fc33 100644 --- a/lib/configuration.php +++ b/lib/configuration.php @@ -41,10 +41,10 @@ class configuration 'sizelimit' => 2097152, 'template' => 'bootstrap', 'notice' => '', - 'base64version' => '2.1.9', 'languageselection' => false, 'languagedefault' => '', 'urlshortener' => '', + 'zerobincompatibility' => false, ), 'expire' => array( 'default' => '1week', diff --git a/lib/model/paste.php b/lib/model/paste.php index 9dc2ab0..1aadfbc 100644 --- a/lib/model/paste.php +++ b/lib/model/paste.php @@ -27,7 +27,7 @@ class model_paste extends model_abstract public function get() { $this->_data = $this->_store->read($this->getId()); - // See if paste has expired and delete it if neccessary. + // check if paste has expired and delete it if neccessary. if (property_exists($this->_data->meta, 'expire_date')) { if ($this->_data->meta->expire_date < time()) @@ -52,6 +52,12 @@ class model_paste extends model_abstract $this->_data->meta->formatter = $this->_conf->getKey('defaultformatter'); } } + + // support old paste format with server wide salt + if (!property_exists($this->_data->meta, 'salt')) + { + $this->_data->meta->salt = serversalt::get(); + } $this->_data->comments = array_values($this->getComments()); $this->_data->comment_count = count($this->_data->comments); $this->_data->comment_offset = 0; @@ -73,6 +79,7 @@ class model_paste extends model_abstract throw new Exception('You are unlucky. Try again.', 75); $this->_data->meta->postdate = time(); + $this->_data->meta->salt = serversalt::generate(); // store paste if ( @@ -151,7 +158,12 @@ class model_paste extends model_abstract */ public function getDeleteToken() { - return hash_hmac('sha1', $this->getId(), serversalt::get()); + if (!property_exists($this->_data->meta, 'salt')) $this->get(); + return hash_hmac( + $this->_conf->getKey('zerobincompatibility') ? 'sha1' : 'sha256', + $this->getId(), + $this->_data->meta->salt + ); } /** diff --git a/lib/zerobin.php b/lib/zerobin.php index 94026de..a6f5406 100644 --- a/lib/zerobin.php +++ b/lib/zerobin.php @@ -327,7 +327,6 @@ class zerobin else { // Make sure the token is valid. - serversalt::setPath($this->_conf->getKey('dir', 'traffic')); if (filter::slow_equals($deletetoken, $paste->getDeleteToken())) { // Paste exists and deletion token is valid: Delete the paste. @@ -364,6 +363,7 @@ class zerobin { $data = $paste->get(); $this->_doesExpire = property_exists($data, 'meta') && property_exists($data->meta, 'expire_date'); + if (property_exists($data->meta, 'salt')) unset($data->meta->salt); $this->_data = json_encode($data); } else @@ -439,7 +439,7 @@ class zerobin $page->assign('BURNAFTERREADINGSELECTED', $this->_conf->getKey('burnafterreadingselected')); $page->assign('PASSWORD', $this->_conf->getKey('password')); $page->assign('FILEUPLOAD', $this->_conf->getKey('fileupload')); - $page->assign('BASE64JSVERSION', $this->_conf->getKey('base64version')); + $page->assign('BASE64JSVERSION', $this->_conf->getKey('zerobincompatibility') ? '1.7' : '2.1.9'); $page->assign('LANGUAGESELECTION', $languageselection); $page->assign('LANGUAGES', i18n::getLanguageLabels(i18n::getAvailableLanguages())); $page->assign('EXPIRE', $expire); diff --git a/tst/bootstrap.php b/tst/bootstrap.php index a207e14..071e172 100644 --- a/tst/bootstrap.php +++ b/tst/bootstrap.php @@ -85,6 +85,7 @@ class helper public static function getPasteWithAttachment($meta = array()) { $example = self::$paste; + $example['meta']['salt'] = serversalt::generate(); $example['meta'] = array_merge($example['meta'], $meta); return $example; } @@ -97,6 +98,8 @@ class helper public static function getPasteAsJson($meta = array()) { $example = self::getPaste(); + // the JSON shouldn't contain the salt + unset($example['meta']['salt']); if (count($meta)) $example['meta'] = $meta; $example['comments'] = array(); diff --git a/tst/configuration.php b/tst/configuration.php index b9084cd..ac3667b 100644 --- a/tst/configuration.php +++ b/tst/configuration.php @@ -13,10 +13,10 @@ class configurationTest extends PHPUnit_Framework_TestCase 'sizelimit' => 2097152, 'template' => 'bootstrap', 'notice' => '', - 'base64version' => '2.1.9', 'languageselection' => false, 'languagedefault' => '', 'urlshortener' => '', + 'zerobincompatibility' => false, ), 'expire' => array( 'default' => '1week', diff --git a/tst/jsonApi.php b/tst/jsonApi.php index 59cfb15..deeaa60 100644 --- a/tst/jsonApi.php +++ b/tst/jsonApi.php @@ -46,13 +46,14 @@ class jsonApiTest extends PHPUnit_Framework_TestCase $content = ob_get_contents(); $response = json_decode($content, true); $this->assertEquals(0, $response['status'], 'outputs status'); + $this->assertStringEndsWith('?' . $response['id'], $response['url'], 'returned URL points to new paste'); + $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data'); + $paste = $this->_model->read($response['id']); $this->assertEquals( - hash_hmac('sha1', $response['id'], serversalt::get()), + hash_hmac('sha256', $response['id'], $paste->meta->salt), $response['deletetoken'], 'outputs valid delete token' ); - $this->assertStringEndsWith('?' . $response['id'], $response['url'], 'returned URL points to new paste'); - $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data'); } /** @@ -80,13 +81,14 @@ class jsonApiTest extends PHPUnit_Framework_TestCase $response = json_decode($content, true); $this->assertEquals(0, $response['status'], 'outputs status'); $this->assertEquals(helper::getPasteId(), $response['id'], 'outputted paste ID matches input'); + $this->assertStringEndsWith('?' . $response['id'], $response['url'], 'returned URL points to new paste'); + $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data'); + $paste = $this->_model->read($response['id']); $this->assertEquals( - hash_hmac('sha1', $response['id'], serversalt::get()), + hash_hmac('sha256', $response['id'], $paste->meta->salt), $response['deletetoken'], 'outputs valid delete token' ); - $this->assertStringEndsWith('?' . $response['id'], $response['url'], 'returned URL points to new paste'); - $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data'); } /** @@ -97,9 +99,10 @@ class jsonApiTest extends PHPUnit_Framework_TestCase $this->reset(); $this->_model->create(helper::getPasteId(), helper::getPaste()); $this->assertTrue($this->_model->exists(helper::getPasteId()), 'paste exists before deleting data'); + $paste = $this->_model->read(helper::getPasteId()); $file = tempnam(sys_get_temp_dir(), 'FOO'); file_put_contents($file, http_build_query(array( - 'deletetoken' => hash_hmac('sha1', helper::getPasteId(), serversalt::get()), + 'deletetoken' => hash_hmac('sha256', helper::getPasteId(), $paste->meta->salt), ))); request::setInputStream($file); $_SERVER['QUERY_STRING'] = helper::getPasteId(); @@ -121,9 +124,10 @@ class jsonApiTest extends PHPUnit_Framework_TestCase $this->reset(); $this->_model->create(helper::getPasteId(), helper::getPaste()); $this->assertTrue($this->_model->exists(helper::getPasteId()), 'paste exists before deleting data'); + $paste = $this->_model->read(helper::getPasteId()); $_POST = array( 'action' => 'delete', - 'deletetoken' => hash_hmac('sha1', helper::getPasteId(), serversalt::get()), + 'deletetoken' => hash_hmac('sha256', helper::getPasteId(), $paste->meta->salt), ); $_SERVER['QUERY_STRING'] = helper::getPasteId(); $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest'; @@ -254,4 +258,4 @@ class jsonApiTest extends PHPUnit_Framework_TestCase $this->assertEquals('{}', $content, 'does not output nasty data'); } -} \ No newline at end of file +} diff --git a/tst/zerobin.php b/tst/zerobin.php index dc404a5..48d54c5 100644 --- a/tst/zerobin.php +++ b/tst/zerobin.php @@ -169,12 +169,13 @@ class zerobinTest extends PHPUnit_Framework_TestCase $content = ob_get_contents(); $response = json_decode($content, true); $this->assertEquals(0, $response['status'], 'outputs status'); + $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data'); + $paste = $this->_model->read($response['id']); $this->assertEquals( - hash_hmac('sha1', $response['id'], serversalt::get()), + hash_hmac('sha256', $response['id'], $paste->meta->salt), $response['deletetoken'], 'outputs valid delete token' ); - $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data'); } /** @@ -288,13 +289,13 @@ class zerobinTest extends PHPUnit_Framework_TestCase $content = ob_get_contents(); $response = json_decode($content, true); $this->assertEquals(0, $response['status'], 'outputs status'); + $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data'); + $paste = $this->_model->read($response['id']); $this->assertEquals( - hash_hmac('sha1', $response['id'], serversalt::get()), + hash_hmac('sha256', $response['id'], $paste->meta->salt), $response['deletetoken'], 'outputs valid delete token' ); - $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data'); - $paste = $this->_model->read($response['id']); $this->assertGreaterThanOrEqual($time + 300, $paste->meta->expire_date, 'time is set correctly'); } @@ -320,13 +321,13 @@ class zerobinTest extends PHPUnit_Framework_TestCase $content = ob_get_contents(); $response = json_decode($content, true); $this->assertEquals(0, $response['status'], 'outputs status'); + $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data'); + $paste = $this->_model->read($response['id']); $this->assertEquals( - hash_hmac('sha1', $response['id'], serversalt::get()), + hash_hmac('sha256', $response['id'], $paste->meta->salt), $response['deletetoken'], 'outputs valid delete token' ); - $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data'); - $paste = $this->_model->read($response['id']); $this->assertGreaterThanOrEqual($time + 300, $paste->meta->expire_date, 'time is set correctly'); $this->assertEquals(1, $paste->meta->opendiscussion, 'discussion is enabled'); } @@ -351,12 +352,13 @@ class zerobinTest extends PHPUnit_Framework_TestCase $content = ob_get_contents(); $response = json_decode($content, true); $this->assertEquals(0, $response['status'], 'outputs status'); + $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data'); + $paste = $this->_model->read($response['id']); $this->assertEquals( - hash_hmac('sha1', $response['id'], serversalt::get()), + hash_hmac('sha256', $response['id'], $paste->meta->salt), $response['deletetoken'], 'outputs valid delete token' ); - $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data'); } /** @@ -426,17 +428,17 @@ class zerobinTest extends PHPUnit_Framework_TestCase $content = ob_get_contents(); $response = json_decode($content, true); $this->assertEquals(0, $response['status'], 'outputs status'); - $this->assertEquals( - hash_hmac('sha1', $response['id'], serversalt::get()), - $response['deletetoken'], - 'outputs valid delete token' - ); $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data'); $original = json_decode(json_encode($_POST)); $stored = $this->_model->read($response['id']); foreach (array('data', 'attachment', 'attachmentname') as $key) { $this->assertEquals($original->$key, $stored->$key); } + $this->assertEquals( + hash_hmac('sha256', $response['id'], $stored->meta->salt), + $response['deletetoken'], + 'outputs valid delete token' + ); } /** @@ -459,12 +461,13 @@ class zerobinTest extends PHPUnit_Framework_TestCase $content = ob_get_contents(); $response = json_decode($content, true); $this->assertEquals(0, $response['status'], 'outputs status'); + $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data'); + $paste = $this->_model->read($response['id']); $this->assertEquals( - hash_hmac('sha1', $response['id'], serversalt::get()), + hash_hmac('sha256', $response['id'], $paste->meta->salt), $response['deletetoken'], 'outputs valid delete token' ); - $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data'); } /** @@ -705,6 +708,7 @@ class zerobinTest extends PHPUnit_Framework_TestCase ob_start(); new zerobin; $content = ob_get_contents(); + unset($burnPaste['meta']['salt']); $this->assertContains( '