implement version 2 format validation, changing ID checksum algorithm, resolves #49
This commit is contained in:
@@ -177,16 +177,16 @@ class Controller
|
||||
* Store new paste or comment
|
||||
*
|
||||
* POST contains one or both:
|
||||
* data = json encoded SJCL 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)
|
||||
* data = json encoded FormatV2 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:
|
||||
* 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)
|
||||
* 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)
|
||||
* attachmentname = json encoded SJCL 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)
|
||||
* attachmentname = json encoded FormatV2 encrypted text (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.
|
||||
* pasteid (optional) = in discussion, which paste this comment belongs to.
|
||||
*
|
||||
|
||||
114
lib/FormatV2.php
Normal file
114
lib/FormatV2.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace PrivateBin\Model;
|
||||
use Exception;
|
||||
use PrivateBin\Configuration;
|
||||
use PrivateBin\Data\AbstractData;
|
||||
use PrivateBin\Sjcl;
|
||||
use PrivateBin\FormatV2;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
@@ -107,14 +107,13 @@ abstract class AbstractModel
|
||||
*/
|
||||
public function setData($data)
|
||||
{
|
||||
if (!Sjcl::isValid($data)) {
|
||||
if (!FormatV2::isValid($data)) {
|
||||
throw new Exception('Invalid data.', 61);
|
||||
}
|
||||
$this->_data->data = $data;
|
||||
|
||||
// We just want a small hash to avoid collisions:
|
||||
// Half-MD5 (64 bits) will do the trick
|
||||
$this->setId(substr(hash('md5', $data), 0, 16));
|
||||
// calculate a 64 bit checksum to avoid collisions
|
||||
$this->setId(hash('fnv1a64', $data['ct']));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace PrivateBin\Model;
|
||||
use Exception;
|
||||
use Identicon\Identicon;
|
||||
use PrivateBin\Persistence\TrafficLimiter;
|
||||
use PrivateBin\Sjcl;
|
||||
use PrivateBin\FormatV2;
|
||||
use PrivateBin\Vizhash16x16;
|
||||
|
||||
/**
|
||||
@@ -183,7 +183,7 @@ class Comment extends AbstractModel
|
||||
*/
|
||||
public function setNickname($nickname)
|
||||
{
|
||||
if (!Sjcl::isValid($nickname)) {
|
||||
if (!FormatV2::isValid($nickname)) {
|
||||
throw new Exception('Invalid data.', 66);
|
||||
}
|
||||
$this->_data->meta->nickname = $nickname;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace PrivateBin\Model;
|
||||
use Exception;
|
||||
use PrivateBin\Controller;
|
||||
use PrivateBin\Persistence\ServerSalt;
|
||||
use PrivateBin\Sjcl;
|
||||
use PrivateBin\FormatV2;
|
||||
|
||||
/**
|
||||
* Paste
|
||||
@@ -195,7 +195,7 @@ class Paste extends AbstractModel
|
||||
*/
|
||||
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);
|
||||
}
|
||||
$this->_data->meta->attachment = $attachment;
|
||||
@@ -210,7 +210,7 @@ class Paste extends AbstractModel
|
||||
*/
|
||||
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);
|
||||
}
|
||||
$this->_data->meta->attachmentname = $attachmentname;
|
||||
|
||||
103
lib/Sjcl.php
103
lib/Sjcl.php
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user