implement version 2 format validation, changing ID checksum algorithm, resolves #49

This commit is contained in:
El RIDO 2019-05-03 23:03:57 +02:00
parent ed676acac3
commit 3338bd792e
No known key found for this signature in database
GPG key ID: 0F5C940A6BD81F92
12 changed files with 233 additions and 185 deletions

View file

@ -177,16 +177,16 @@ class Controller
* Store new paste or comment * Store new paste or comment
* *
* POST contains one or both: * POST contains one or both:
* data = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct) * data = json encoded FormatV2 encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
* attachment = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct) * attachment = json encoded FormatV2 encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
* *
* All optional data will go to meta information: * All optional data will go to meta information:
* expire (optional) = expiration delay (never,5min,10min,1hour,1day,1week,1month,1year,burn) (default:never) * expire (optional) = expiration delay (never,5min,10min,1hour,1day,1week,1month,1year,burn) (default:never)
* formatter (optional) = format to display the paste as (plaintext,syntaxhighlighting,markdown) (default:syntaxhighlighting) * formatter (optional) = format to display the paste as (plaintext,syntaxhighlighting,markdown) (default:syntaxhighlighting)
* burnafterreading (optional) = if this paste may only viewed once ? (0/1) (default:0) * burnafterreading (optional) = if this paste may only viewed once ? (0/1) (default:0)
* opendiscusssion (optional) = is the discussion allowed on this paste ? (0/1) (default:0) * opendiscusssion (optional) = is the discussion allowed on this paste ? (0/1) (default:0)
* attachmentname = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct) * attachmentname = json encoded FormatV2 encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
* nickname (optional) = in discussion, encoded SJCL encrypted text nickname of author of comment (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct) * nickname (optional) = in discussion, encoded FormatV2 encrypted text nickname of author of comment (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
* parentid (optional) = in discussion, which comment this comment replies to. * parentid (optional) = in discussion, which comment this comment replies to.
* pasteid (optional) = in discussion, which paste this comment belongs to. * pasteid (optional) = in discussion, which paste this comment belongs to.
* *

114
lib/FormatV2.php Normal file
View file

@ -0,0 +1,114 @@
<?php
/**
* PrivateBin
*
* a zero-knowledge paste bin
*
* @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1
*/
namespace PrivateBin;
/**
* FormatV2
*
* Provides validation function for version 2 format of pastes & comments.
*/
class FormatV2
{
/**
* version 2 format validator
*
* Checks if the given array is a proper version 2 formatted, encrypted message.
*
* @access public
* @static
* @param array $message
* @param bool $isComment
* @return bool
*/
public static function isValid($message, $isComment = false)
{
$required_keys = array('adata', 'meta', 'v', 'ct');
if ($isComment) {
$required_keys[] = 'pasteid';
$required_keys[] = 'parentid';
}
// Make sure no additionnal keys were added.
if (count(array_keys($message)) != count($required_keys)) {
return false;
}
// Make sure required fields are present.
foreach ($required_keys as $k) {
if (!array_key_exists($k, $message)) {
return false;
}
}
// Make sure some fields are base64 data:
// - initialization vector
if (!base64_decode($message['adata'][0][0], true)) {
return false;
}
// - salt
if (!base64_decode($message['adata'][0][1], true)) {
return false;
}
// - cipher text
if (!($ct = base64_decode($message['ct'], true))) {
return false;
}
// Make sure some fields have a reasonable size:
// - initialization vector
if (strlen($message['adata'][0][0]) > 24) {
return false;
}
// - salt
if (strlen($message['adata'][0][1]) > 14) {
return false;
}
// Make sure some fields contain no unsupported values:
// - version
if (!(is_int($message['v']) || is_float($message['v'])) || (float) $message['v'] < 2) {
return false;
}
// - iterations, refuse less then 10000 iterations (minimum NIST recommendation)
if (!is_int($message['adata'][0][2]) || $message['adata'][0][2] <= 10000) {
return false;
}
// - key size
if (!in_array($message['adata'][0][3], array(128, 192, 256), true)) {
return false;
}
// - tag size
if (!in_array($message['adata'][0][4], array(64, 96, 128), true)) {
return false;
}
// - algorithm, must be AES
if ($message['adata'][0][5] !== 'aes') {
return false;
}
// - mode
if (!in_array($message['adata'][0][6], array('ctr', 'cbc', 'gcm'), true)) {
return false;
}
// - compression
if (!in_array($message['adata'][0][7], array('zlib', 'none'), true)) {
return false;
}
// Reject data if entropy is too low
if (strlen($ct) > strlen(gzdeflate($ct))) {
return false;
}
return true;
}
}

View file

@ -15,7 +15,7 @@ namespace PrivateBin\Model;
use Exception; use Exception;
use PrivateBin\Configuration; use PrivateBin\Configuration;
use PrivateBin\Data\AbstractData; use PrivateBin\Data\AbstractData;
use PrivateBin\Sjcl; use PrivateBin\FormatV2;
use stdClass; use stdClass;
/** /**
@ -107,14 +107,13 @@ abstract class AbstractModel
*/ */
public function setData($data) public function setData($data)
{ {
if (!Sjcl::isValid($data)) { if (!FormatV2::isValid($data)) {
throw new Exception('Invalid data.', 61); throw new Exception('Invalid data.', 61);
} }
$this->_data->data = $data; $this->_data->data = $data;
// We just want a small hash to avoid collisions: // calculate a 64 bit checksum to avoid collisions
// Half-MD5 (64 bits) will do the trick $this->setId(hash('fnv1a64', $data['ct']));
$this->setId(substr(hash('md5', $data), 0, 16));
} }
/** /**

View file

@ -15,7 +15,7 @@ namespace PrivateBin\Model;
use Exception; use Exception;
use Identicon\Identicon; use Identicon\Identicon;
use PrivateBin\Persistence\TrafficLimiter; use PrivateBin\Persistence\TrafficLimiter;
use PrivateBin\Sjcl; use PrivateBin\FormatV2;
use PrivateBin\Vizhash16x16; use PrivateBin\Vizhash16x16;
/** /**
@ -183,7 +183,7 @@ class Comment extends AbstractModel
*/ */
public function setNickname($nickname) public function setNickname($nickname)
{ {
if (!Sjcl::isValid($nickname)) { if (!FormatV2::isValid($nickname)) {
throw new Exception('Invalid data.', 66); throw new Exception('Invalid data.', 66);
} }
$this->_data->meta->nickname = $nickname; $this->_data->meta->nickname = $nickname;

View file

@ -15,7 +15,7 @@ namespace PrivateBin\Model;
use Exception; use Exception;
use PrivateBin\Controller; use PrivateBin\Controller;
use PrivateBin\Persistence\ServerSalt; use PrivateBin\Persistence\ServerSalt;
use PrivateBin\Sjcl; use PrivateBin\FormatV2;
/** /**
* Paste * Paste
@ -195,7 +195,7 @@ class Paste extends AbstractModel
*/ */
public function setAttachment($attachment) public function setAttachment($attachment)
{ {
if (!$this->_conf->getKey('fileupload') || !Sjcl::isValid($attachment)) { if (!$this->_conf->getKey('fileupload') || !FormatV2::isValid($attachment)) {
throw new Exception('Invalid attachment.', 71); throw new Exception('Invalid attachment.', 71);
} }
$this->_data->meta->attachment = $attachment; $this->_data->meta->attachment = $attachment;
@ -210,7 +210,7 @@ class Paste extends AbstractModel
*/ */
public function setAttachmentName($attachmentname) public function setAttachmentName($attachmentname)
{ {
if (!$this->_conf->getKey('fileupload') || !Sjcl::isValid($attachmentname)) { if (!$this->_conf->getKey('fileupload') || !FormatV2::isValid($attachmentname)) {
throw new Exception('Invalid attachment.', 72); throw new Exception('Invalid attachment.', 72);
} }
$this->_data->meta->attachmentname = $attachmentname; $this->_data->meta->attachmentname = $attachmentname;

View file

@ -1,103 +0,0 @@
<?php
/**
* PrivateBin
*
* a zero-knowledge paste bin
*
* @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1
*/
namespace PrivateBin;
/**
* Sjcl
*
* Provides SJCL validation function.
*/
class Sjcl
{
/**
* SJCL validator
*
* Checks if a json string is a proper SJCL encrypted message.
*
* @access public
* @static
* @param string $encoded JSON
* @return bool
*/
public static function isValid($encoded)
{
$accepted_keys = array('iv', 'v', 'iter', 'ks', 'ts', 'mode', 'adata', 'cipher', 'salt', 'ct');
// Make sure content is valid json
$decoded = json_decode($encoded);
if (is_null($decoded)) {
return false;
}
$decoded = (array) $decoded;
// Make sure no additionnal keys were added.
if (
count(array_keys($decoded)) != count($accepted_keys)
) {
return false;
}
// Make sure required fields are present and contain base64 data.
foreach ($accepted_keys as $k) {
if (!array_key_exists($k, $decoded)) {
return false;
}
}
// Make sure some fields are base64 data.
if (!base64_decode($decoded['iv'], true)) {
return false;
}
if (!base64_decode($decoded['salt'], true)) {
return false;
}
if (!($ct = base64_decode($decoded['ct'], true))) {
return false;
}
// Make sure some fields have a reasonable size.
if (strlen($decoded['iv']) > 24) {
return false;
}
if (strlen($decoded['salt']) > 14) {
return false;
}
// Make sure some fields contain no unsupported values.
if (!(is_int($decoded['v']) || is_float($decoded['v'])) || (float) $decoded['v'] < 1) {
return false;
}
if (!is_int($decoded['iter']) || $decoded['iter'] <= 100) {
return false;
}
if (!in_array($decoded['ks'], array(128, 192, 256), true)) {
return false;
}
if (!in_array($decoded['ts'], array(64, 96, 128), true)) {
return false;
}
if (!in_array($decoded['mode'], array('ccm', 'ocb2', 'gcm'), true)) {
return false;
}
if ($decoded['cipher'] !== 'aes') {
return false;
}
// Reject data if entropy is too low
if (strlen($ct) > strlen(gzdeflate($ct))) {
return false;
}
return true;
}
}

View file

@ -28,7 +28,7 @@ class Helper
* *
* @var string * @var string
*/ */
private static $pasteid = '5e9bc25c89fb3bf9'; private static $pasteid = '5b65a01b43987bc2';
/** /**
* example paste version 1 * example paste version 1

71
tst/FormatV2Test.php Normal file
View file

@ -0,0 +1,71 @@
<?php
use PrivateBin\FormatV2;
class FormatV2Test extends PHPUnit_Framework_TestCase
{
public function testFormatV2ValidatorValidatesCorrectly()
{
$this->assertTrue(FormatV2::isValid(Helper::getPaste()), 'valid format');
$this->assertTrue(FormatV2::isValid(Helper::getComment(), true), 'valid format');
$paste = Helper::getPaste();
$paste['adata'][0][0] = '$';
$this->assertFalse(FormatV2::isValid($paste), 'invalid base64 encoding of iv');
$paste = Helper::getPaste();
$paste['adata'][0][1] = '$';
$this->assertFalse(FormatV2::isValid($paste), 'invalid base64 encoding of salt');
$paste = Helper::getPaste();
$paste['ct'] = '$';
$this->assertFalse(FormatV2::isValid($paste), 'invalid base64 encoding of ct');
$paste = Helper::getPaste();
$paste['ct'] = 'bm9kYXRhbm9kYXRhbm9kYXRhbm9kYXRhbm9kYXRhCg==';
$this->assertFalse(FormatV2::isValid($paste), 'low ct entropy');
$paste = Helper::getPaste();
$paste['adata'][0][0] = 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=';
$this->assertFalse(FormatV2::isValid($paste), 'iv too long');
$paste = Helper::getPaste();
$paste['adata'][0][1] = 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=';
$this->assertFalse(FormatV2::isValid($paste), 'salt too long');
$paste = Helper::getPaste();
$paste['foo'] = 'bar';
$this->assertFalse(FormatV2::isValid($paste), 'invalid additional key');
unset($paste['meta']);
$this->assertFalse(FormatV2::isValid($paste), 'invalid missing key');
$paste = Helper::getPaste();
$paste['v'] = 0.9;
$this->assertFalse(FormatV2::isValid($paste), 'unsupported version');
$paste = Helper::getPaste();
$paste['adata'][0][2] = 1000;
$this->assertFalse(FormatV2::isValid($paste), 'not enough iterations');
$paste = Helper::getPaste();
$paste['adata'][0][3] = 127;
$this->assertFalse(FormatV2::isValid($paste), 'invalid key size');
$paste = Helper::getPaste();
$paste['adata'][0][4] = 63;
$this->assertFalse(FormatV2::isValid($paste), 'invalid tag length');
$paste = Helper::getPaste();
$paste['adata'][0][5] = '!#@';
$this->assertFalse(FormatV2::isValid($paste), 'invalid algorithm');
$paste = Helper::getPaste();
$paste['adata'][0][6] = '!#@';
$this->assertFalse(FormatV2::isValid($paste), 'invalid mode');
$paste = Helper::getPaste();
$paste['adata'][0][7] = '!#@';
$this->assertFalse(FormatV2::isValid($paste), 'invalid compression');
}
}

View file

@ -60,15 +60,15 @@ class ModelTest extends PHPUnit_Framework_TestCase
$this->assertFalse($paste->exists(), 'paste does not yet exist'); $this->assertFalse($paste->exists(), 'paste does not yet exist');
$paste = $this->_model->getPaste(); $paste = $this->_model->getPaste();
$paste->setData($pasteData['data']); $paste->setData($pasteData);
$paste->setOpendiscussion(); $paste->setOpendiscussion();
$paste->setFormatter($pasteData['meta']['formatter']); $paste->setFormatter($pasteData['adata'][1]);
$paste->store(); $paste->store();
$paste = $this->_model->getPaste(Helper::getPasteId()); $paste = $this->_model->getPaste(Helper::getPasteId());
$this->assertTrue($paste->exists(), 'paste exists after storing it'); $this->assertTrue($paste->exists(), 'paste exists after storing it');
$paste = $paste->get(); $paste = $paste->get();
$this->assertEquals($pasteData['data'], $paste->data); $this->assertEquals($pasteData, $paste->data);
foreach (array('opendiscussion', 'formatter') as $key) { foreach (array('opendiscussion', 'formatter') as $key) {
$this->assertEquals($pasteData['meta'][$key], $paste->meta->$key); $this->assertEquals($pasteData['meta'][$key], $paste->meta->$key);
} }
@ -80,7 +80,7 @@ class ModelTest extends PHPUnit_Framework_TestCase
$this->assertFalse($comment->exists(), 'comment does not yet exist'); $this->assertFalse($comment->exists(), 'comment does not yet exist');
$comment = $paste->getComment(Helper::getPasteId()); $comment = $paste->getComment(Helper::getPasteId());
$comment->setData($commentData['data']); $comment->setData($commentData);
$comment->setNickname($commentData['meta']['nickname']); $comment->setNickname($commentData['meta']['nickname']);
$comment->getParentId(); $comment->getParentId();
$comment->store(); $comment->store();
@ -88,7 +88,7 @@ class ModelTest extends PHPUnit_Framework_TestCase
$comment = $paste->getComment(Helper::getPasteId(), Helper::getCommentId()); $comment = $paste->getComment(Helper::getPasteId(), Helper::getCommentId());
$this->assertTrue($comment->exists(), 'comment exists after storing it'); $this->assertTrue($comment->exists(), 'comment exists after storing it');
$comment = $comment->get(); $comment = $comment->get();
$this->assertEquals($commentData['data'], $comment->data); $this->assertEquals($commentData, $comment->data);
$this->assertEquals($commentData['meta']['nickname'], $comment->meta->nickname); $this->assertEquals($commentData['meta']['nickname'], $comment->meta->nickname);
// deleting pastes // deleting pastes
@ -108,15 +108,15 @@ class ModelTest extends PHPUnit_Framework_TestCase
$this->_model->getPaste(Helper::getPasteId())->delete(); $this->_model->getPaste(Helper::getPasteId())->delete();
$paste = $this->_model->getPaste(); $paste = $this->_model->getPaste();
$paste->setData($pasteData['data']); $paste->setData($pasteData);
$paste->setOpendiscussion(); $paste->setOpendiscussion();
$paste->setFormatter($pasteData['meta']['formatter']); $paste->setFormatter($pasteData['adata'][1]);
$paste->store(); $paste->store();
$paste = $this->_model->getPaste(); $paste = $this->_model->getPaste();
$paste->setData($pasteData['data']); $paste->setData($pasteData);
$paste->setOpendiscussion(); $paste->setOpendiscussion();
$paste->setFormatter($pasteData['meta']['formatter']); $paste->setFormatter($pasteData['adata'][1]);
$paste->store(); $paste->store();
} }
@ -131,18 +131,18 @@ class ModelTest extends PHPUnit_Framework_TestCase
$this->_model->getPaste(Helper::getPasteId())->delete(); $this->_model->getPaste(Helper::getPasteId())->delete();
$paste = $this->_model->getPaste(); $paste = $this->_model->getPaste();
$paste->setData($pasteData['data']); $paste->setData($pasteData);
$paste->setOpendiscussion(); $paste->setOpendiscussion();
$paste->setFormatter($pasteData['meta']['formatter']); $paste->setFormatter($pasteData['adata'][1]);
$paste->store(); $paste->store();
$comment = $paste->getComment(Helper::getPasteId()); $comment = $paste->getComment(Helper::getPasteId());
$comment->setData($commentData['data']); $comment->setData($commentData);
$comment->setNickname($commentData['meta']['nickname']); $comment->setNickname($commentData['meta']['nickname']);
$comment->store(); $comment->store();
$comment = $paste->getComment(Helper::getPasteId()); $comment = $paste->getComment(Helper::getPasteId());
$comment->setData($commentData['data']); $comment->setData($commentData);
$comment->setNickname($commentData['meta']['nickname']); $comment->setNickname($commentData['meta']['nickname']);
$comment->store(); $comment->store();
} }
@ -154,7 +154,7 @@ class ModelTest extends PHPUnit_Framework_TestCase
$this->_model->getPaste(Helper::getPasteId())->delete(); $this->_model->getPaste(Helper::getPasteId())->delete();
$paste = $this->_model->getPaste(); $paste = $this->_model->getPaste();
$paste->setData($pasteData['data']); $paste->setData($pasteData);
$paste->setBurnafterreading(); $paste->setBurnafterreading();
$paste->setOpendiscussion(); $paste->setOpendiscussion();
// not setting a formatter, should use default one // not setting a formatter, should use default one
@ -167,13 +167,13 @@ class ModelTest extends PHPUnit_Framework_TestCase
$this->_model->getPaste(Helper::getPasteId())->delete(); $this->_model->getPaste(Helper::getPasteId())->delete();
$paste = $this->_model->getPaste(); $paste = $this->_model->getPaste();
$paste->setData($pasteData['data']); $paste->setData($pasteData);
$paste->setBurnafterreading('0'); $paste->setBurnafterreading('0');
$paste->setOpendiscussion(); $paste->setOpendiscussion();
$paste->store(); $paste->store();
$comment = $paste->getComment(Helper::getPasteId()); $comment = $paste->getComment(Helper::getPasteId());
$comment->setData($commentData['data']); $comment->setData($commentData);
$comment->setNickname($commentData['meta']['nickname']); $comment->setNickname($commentData['meta']['nickname']);
$comment->store(); $comment->store();
@ -208,7 +208,7 @@ class ModelTest extends PHPUnit_Framework_TestCase
public function testInvalidData() public function testInvalidData()
{ {
$paste = $this->_model->getPaste(); $paste = $this->_model->getPaste();
$paste->setData(''); $paste->setData(array());
} }
/** /**
@ -229,7 +229,7 @@ class ModelTest extends PHPUnit_Framework_TestCase
{ {
$pasteData = Helper::getPaste(); $pasteData = Helper::getPaste();
$paste = $this->_model->getPaste(Helper::getPasteId()); $paste = $this->_model->getPaste(Helper::getPasteId());
$paste->setData($pasteData['data']); $paste->setData($pasteData);
$paste->store(); $paste->store();
$comment = $paste->getComment(Helper::getPasteId()); $comment = $paste->getComment(Helper::getPasteId());
@ -245,7 +245,7 @@ class ModelTest extends PHPUnit_Framework_TestCase
{ {
$pasteData = Helper::getPaste(); $pasteData = Helper::getPaste();
$paste = $this->_model->getPaste(Helper::getPasteId()); $paste = $this->_model->getPaste(Helper::getPasteId());
$paste->setData($pasteData['data']); $paste->setData($pasteData);
$paste->store(); $paste->store();
$comment = $paste->getComment(Helper::getPasteId()); $comment = $paste->getComment(Helper::getPasteId());
@ -260,7 +260,7 @@ class ModelTest extends PHPUnit_Framework_TestCase
$this->assertFalse($paste->exists(), 'paste does not yet exist'); $this->assertFalse($paste->exists(), 'paste does not yet exist');
$paste = $this->_model->getPaste(); $paste = $this->_model->getPaste();
$paste->setData($pasteData['data']); $paste->setData($pasteData);
$paste->setExpiration('5min'); // = 300 seconds $paste->setExpiration('5min'); // = 300 seconds
$paste->store(); $paste->store();
@ -278,7 +278,7 @@ class ModelTest extends PHPUnit_Framework_TestCase
$this->_model->getPaste(Helper::getPasteId())->delete(); $this->_model->getPaste(Helper::getPasteId())->delete();
$paste = $this->_model->getPaste(); $paste = $this->_model->getPaste();
$paste->setData($pasteData['data']); $paste->setData($pasteData);
$paste->store(); $paste->store();
$paste->getComment(Helper::getPasteId())->delete(); $paste->getComment(Helper::getPasteId())->delete();
} }
@ -336,15 +336,15 @@ class ModelTest extends PHPUnit_Framework_TestCase
$this->assertFalse($paste->exists(), 'paste does not yet exist'); $this->assertFalse($paste->exists(), 'paste does not yet exist');
$paste = $model->getPaste(); $paste = $model->getPaste();
$paste->setData($pasteData['data']); $paste->setData($pasteData);
$paste->setOpendiscussion(); $paste->setOpendiscussion();
$paste->setFormatter($pasteData['meta']['formatter']); $paste->setFormatter($pasteData['adata'][1]);
$paste->store(); $paste->store();
$paste = $model->getPaste(Helper::getPasteId()); $paste = $model->getPaste(Helper::getPasteId());
$this->assertTrue($paste->exists(), 'paste exists after storing it'); $this->assertTrue($paste->exists(), 'paste exists after storing it');
$paste = $paste->get(); $paste = $paste->get();
$this->assertEquals($pasteData['data'], $paste->data); $this->assertEquals($pasteData, $paste->data);
foreach (array('opendiscussion', 'formatter') as $key) { foreach (array('opendiscussion', 'formatter') as $key) {
$this->assertEquals($pasteData['meta'][$key], $paste->meta->$key); $this->assertEquals($pasteData['meta'][$key], $paste->meta->$key);
} }
@ -356,14 +356,14 @@ class ModelTest extends PHPUnit_Framework_TestCase
$this->assertFalse($comment->exists(), 'comment does not yet exist'); $this->assertFalse($comment->exists(), 'comment does not yet exist');
$comment = $paste->getComment(Helper::getPasteId()); $comment = $paste->getComment(Helper::getPasteId());
$comment->setData($commentData['data']); $comment->setData($commentData);
$comment->setNickname($commentData['meta']['nickname']); $comment->setNickname($commentData['meta']['nickname']);
$comment->store(); $comment->store();
$comment = $paste->getComment(Helper::getPasteId(), Helper::getCommentId()); $comment = $paste->getComment(Helper::getPasteId(), Helper::getCommentId());
$this->assertTrue($comment->exists(), 'comment exists after storing it'); $this->assertTrue($comment->exists(), 'comment exists after storing it');
$comment = $comment->get(); $comment = $comment->get();
$this->assertEquals($commentData['data'], $comment->data); $this->assertEquals($commentData, $comment->data);
$this->assertEquals($commentData['meta']['nickname'], $comment->meta->nickname); $this->assertEquals($commentData['meta']['nickname'], $comment->meta->nickname);
$this->assertFalse(property_exists($comment->meta, 'vizhash'), 'vizhash was not generated'); $this->assertFalse(property_exists($comment->meta, 'vizhash'), 'vizhash was not generated');
} }
@ -389,13 +389,13 @@ class ModelTest extends PHPUnit_Framework_TestCase
$model->getPaste(Helper::getPasteId())->delete(); $model->getPaste(Helper::getPasteId())->delete();
$paste = $model->getPaste(); $paste = $model->getPaste();
$paste->setData($pasteData['data']); $paste->setData($pasteData);
$paste->setOpendiscussion(); $paste->setOpendiscussion();
$paste->setFormatter($pasteData['meta']['formatter']); $paste->setFormatter($pasteData['adata'][1]);
$paste->store(); $paste->store();
$comment = $paste->getComment(Helper::getPasteId()); $comment = $paste->getComment(Helper::getPasteId());
$comment->setData($commentData['data']); $comment->setData($commentData);
$comment->setNickname($commentData['meta']['nickname']); $comment->setNickname($commentData['meta']['nickname']);
$comment->store(); $comment->store();
@ -426,13 +426,13 @@ class ModelTest extends PHPUnit_Framework_TestCase
$model->getPaste(Helper::getPasteId())->delete(); $model->getPaste(Helper::getPasteId())->delete();
$paste = $model->getPaste(); $paste = $model->getPaste();
$paste->setData($pasteData['data']); $paste->setData($pasteData);
$paste->setOpendiscussion(); $paste->setOpendiscussion();
$paste->setFormatter($pasteData['meta']['formatter']); $paste->setFormatter($pasteData['adata'][1]);
$paste->store(); $paste->store();
$comment = $paste->getComment(Helper::getPasteId()); $comment = $paste->getComment(Helper::getPasteId());
$comment->setData($commentData['data']); $comment->setData($commentData);
$comment->setNickname($commentData['meta']['nickname']); $comment->setNickname($commentData['meta']['nickname']);
$comment->store(); $comment->store();

View file

@ -1,33 +0,0 @@
<?php
use PrivateBin\Persistence\ServerSalt;
use PrivateBin\Sjcl;
class SjclTest extends PHPUnit_Framework_TestCase
{
public function testSjclValidatorValidatesCorrectly()
{
ServerSalt::setPath(sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data');
$paste = Helper::getPasteWithAttachment();
$this->assertTrue(Sjcl::isValid($paste['data']), 'valid sjcl');
$this->assertTrue(Sjcl::isValid($paste['attachment']), 'valid sjcl');
$this->assertTrue(Sjcl::isValid($paste['attachmentname']), 'valid sjcl');
$this->assertTrue(Sjcl::isValid(Helper::getComment()['data']), 'valid sjcl');
$this->assertTrue(Sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gx1vA2/gQ3U","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'valid sjcl');
$this->assertFalse(Sjcl::isValid('{"iv":"$","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gx1vA2/gQ3U","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'invalid base64 encoding of iv');
$this->assertFalse(Sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"$","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'invalid base64 encoding of salt');
$this->assertFalse(Sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gx1vA2/gQ3U","ct":"$"}'), 'invalid base64 encoding of ct');
$this->assertFalse(Sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gx1vA2/gQ3U","ct":"bm9kYXRhbm9kYXRhbm9kYXRhbm9kYXRhbm9kYXRhCg=="}'), 'low ct entropy');
$this->assertFalse(Sjcl::isValid('{"iv":"MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gx1vA2/gQ3U","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'iv to long');
$this->assertFalse(Sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'salt to long');
$this->assertFalse(Sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gx1vA2/gQ3U","ct":"j7ImByuE5xCqD2YXm6aSyA","foo":"MTIzNDU2Nzg5MDEyMzQ1Njc4OTA="}'), 'invalid additional key');
$this->assertFalse(Sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","v":0.9,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gx1vA2/gQ3U","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'unsupported version');
$this->assertFalse(Sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","v":1,"iter":100,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gx1vA2/gQ3U","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'not enough iterations');
$this->assertFalse(Sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","v":1,"iter":1000,"ks":127,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gx1vA2/gQ3U","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'invalid key size');
$this->assertFalse(Sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","v":1,"iter":1000,"ks":128,"ts":63,"mode":"ccm","adata":"","cipher":"aes","salt":"Gx1vA2/gQ3U","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'invalid tag length');
$this->assertFalse(Sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","v":1,"iter":1000,"ks":128,"ts":64,"mode":"!#@","adata":"","cipher":"aes","salt":"Gx1vA2/gQ3U","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'invalid mode');
$this->assertFalse(Sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"!#@","salt":"Gx1vA2/gQ3U","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'invalid cipher');
// @note adata is not validated, except as part of the total message length
}
}

View file

@ -29,7 +29,7 @@ return array(
'PrivateBin\\Persistence\\ServerSalt' => $baseDir . '/lib/Persistence/ServerSalt.php', 'PrivateBin\\Persistence\\ServerSalt' => $baseDir . '/lib/Persistence/ServerSalt.php',
'PrivateBin\\Persistence\\TrafficLimiter' => $baseDir . '/lib/Persistence/TrafficLimiter.php', 'PrivateBin\\Persistence\\TrafficLimiter' => $baseDir . '/lib/Persistence/TrafficLimiter.php',
'PrivateBin\\Request' => $baseDir . '/lib/Request.php', 'PrivateBin\\Request' => $baseDir . '/lib/Request.php',
'PrivateBin\\Sjcl' => $baseDir . '/lib/Sjcl.php', 'PrivateBin\\FormatV2' => $baseDir . '/lib/FormatV2.php',
'PrivateBin\\View' => $baseDir . '/lib/View.php', 'PrivateBin\\View' => $baseDir . '/lib/View.php',
'PrivateBin\\Vizhash16x16' => $baseDir . '/lib/Vizhash16x16.php', 'PrivateBin\\Vizhash16x16' => $baseDir . '/lib/Vizhash16x16.php',
); );

View file

@ -58,7 +58,7 @@ class ComposerStaticInitDontChange
'PrivateBin\\Persistence\\ServerSalt' => __DIR__ . '/../..' . '/lib/Persistence/ServerSalt.php', 'PrivateBin\\Persistence\\ServerSalt' => __DIR__ . '/../..' . '/lib/Persistence/ServerSalt.php',
'PrivateBin\\Persistence\\TrafficLimiter' => __DIR__ . '/../..' . '/lib/Persistence/TrafficLimiter.php', 'PrivateBin\\Persistence\\TrafficLimiter' => __DIR__ . '/../..' . '/lib/Persistence/TrafficLimiter.php',
'PrivateBin\\Request' => __DIR__ . '/../..' . '/lib/Request.php', 'PrivateBin\\Request' => __DIR__ . '/../..' . '/lib/Request.php',
'PrivateBin\\Sjcl' => __DIR__ . '/../..' . '/lib/Sjcl.php', 'PrivateBin\\FormatV2' => __DIR__ . '/../..' . '/lib/FormatV2.php',
'PrivateBin\\View' => __DIR__ . '/../..' . '/lib/View.php', 'PrivateBin\\View' => __DIR__ . '/../..' . '/lib/View.php',
'PrivateBin\\Vizhash16x16' => __DIR__ . '/../..' . '/lib/Vizhash16x16.php', 'PrivateBin\\Vizhash16x16' => __DIR__ . '/../..' . '/lib/Vizhash16x16.php',
); );