6 Commits

46 changed files with 466 additions and 144 deletions

1
.gitattributes vendored
View File

@@ -18,4 +18,3 @@ js/test/ export-ignore
.styleci.yml export-ignore .styleci.yml export-ignore
.travis.yml export-ignore .travis.yml export-ignore
composer.json export-ignore composer.json export-ignore
BADGES.md export-ignore

View File

@@ -1,9 +0,0 @@
# Badges
[![Build Status](https://travis-ci.org/PrivateBin/PrivateBin.svg?branch=master)](https://travis-ci.org/PrivateBin/PrivateBin) [![Build Status](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/badges/build.png?b=master)](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/build-status/master)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/094500f62abf4c9aa0c8a8a4520e4789)](https://www.codacy.com/app/PrivateBin/PrivateBin)
[![Code Climate](https://codeclimate.com/github/PrivateBin/PrivateBin/badges/gpa.svg)](https://codeclimate.com/github/PrivateBin/PrivateBin)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/?branch=master)
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/57c9e74e-c6f9-4de6-a876-df66ec2ea1ff/mini.png)](https://insight.sensiolabs.com/projects/57c9e74e-c6f9-4de6-a876-df66ec2ea1ff)
[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/094500f62abf4c9aa0c8a8a4520e4789)](https://www.codacy.com/app/PrivateBin/PrivateBin)
[![Test Coverage](https://codeclimate.com/github/PrivateBin/PrivateBin/badges/coverage.svg)](https://codeclimate.com/github/PrivateBin/PrivateBin/coverage) [![Code Coverage](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/?branch=master)

View File

@@ -165,7 +165,7 @@ CREATE INDEX parent ON prefix_comment(pasteid);
CREATE TABLE prefix_config ( CREATE TABLE prefix_config (
id CHAR(16) NOT NULL, value TEXT, PRIMARY KEY (id) id CHAR(16) NOT NULL, value TEXT, PRIMARY KEY (id)
); );
INSERT INTO prefix_config VALUES('VERSION', '1.3'); INSERT INTO prefix_config VALUES('VERSION', '1.2.1');
``` ```
In **PostgreSQL**, the data, attachment, nickname and vizhash columns needs to be TEXT and not BLOB or MEDIUMBLOB. In **PostgreSQL**, the data, attachment, nickname and vizhash columns needs to be TEXT and not BLOB or MEDIUMBLOB.

View File

@@ -1,6 +1,13 @@
# [![PrivateBin](https://cdn.rawgit.com/PrivateBin/assets/master/images/preview/logoSmall.png)](https://privatebin.info/) # [<img alt="PrivateBin" src="https://cdn.rawgit.com/PrivateBin/assets/master/images/minified/logo.svg" width="500" />](https://privatebin.info/)
[![Build Status](https://travis-ci.org/PrivateBin/PrivateBin.svg?branch=master)](https://travis-ci.org/PrivateBin/PrivateBin) [![Build Status](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/badges/build.png?b=master)](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/build-status/master)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/094500f62abf4c9aa0c8a8a4520e4789)](https://www.codacy.com/app/PrivateBin/PrivateBin)
[![Code Climate](https://codeclimate.com/github/PrivateBin/PrivateBin/badges/gpa.svg)](https://codeclimate.com/github/PrivateBin/PrivateBin)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/?branch=master)
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/57c9e74e-c6f9-4de6-a876-df66ec2ea1ff/mini.png)](https://insight.sensiolabs.com/projects/57c9e74e-c6f9-4de6-a876-df66ec2ea1ff)
[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/094500f62abf4c9aa0c8a8a4520e4789)](https://www.codacy.com/app/PrivateBin/PrivateBin)
[![Test Coverage](https://codeclimate.com/github/PrivateBin/PrivateBin/badges/coverage.svg)](https://codeclimate.com/github/PrivateBin/PrivateBin/coverage) [![Code Coverage](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/?branch=master)
*Current version: 1.3* *Current version: 1.2.1*
**PrivateBin** is a minimalist, open source online [pastebin](https://en.wikipedia.org/wiki/Pastebin) **PrivateBin** is a minimalist, open source online [pastebin](https://en.wikipedia.org/wiki/Pastebin)
where the server has zero knowledge of pasted data. where the server has zero knowledge of pasted data.

View File

@@ -6,7 +6,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
body { body {

View File

@@ -6,7 +6,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
/* When there is no script at all other */ /* When there is no script at all other */

View File

@@ -6,7 +6,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
/* CSS Reset from YUI 3.4.1 (build 4118) - Copyright 2011 Yahoo! Inc. All rights reserved. /* CSS Reset from YUI 3.4.1 (build 4118) - Copyright 2011 Yahoo! Inc. All rights reserved.

View File

@@ -155,11 +155,11 @@
"Could not get paste data: %s": "Could not get paste data: %s":
"Text konnte nicht geladen werden: %s", "Text konnte nicht geladen werden: %s",
"QR code": "QR code", "QR code": "QR code",
"I love you too, bot…": "Ich mag Dich auch, bot…", "I love you too, bot…": "I love you too, bot…",
"This website is using an insecure HTTP connection! Please use it only for testing.": "This website is using an insecure HTTP connection! Please use it only for testing.":
"Diese Webseite verwendet eine unsichere HTTP Verbindung! Bitte benutze sie nur zum Testen.", "This website is using an insecure HTTP connection! Please use it only for testing.",
"For more information <a href=\"%s\">see this FAQ entry</a>.": "For more information <a href=\"%s\">see this FAQ entry</a>.":
"<a href=\"%s\">Besuche diesen FAQ Eintrag</a> für weitere Informationen dazu.", "For more information <a href=\"%s\">see this FAQ entry</a>.",
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
"Dein Browser benötigt möglicherweise eine HTTPS Verbindung um das WebCrypto API nutzen zu können. Versuche <a href=\"%s\">auf HTTPS zu wechseln</a>." "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>."
} }

View File

@@ -87,7 +87,7 @@
"FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.":
"POUR VOS YEUX UNIQUEMENT. Ne fermez pas cette fenêtre, ce paste ne pourra plus être affiché.", "POUR VOS YEUX UNIQUEMENT. Ne fermez pas cette fenêtre, ce paste ne pourra plus être affiché.",
"Could not decrypt comment; Wrong key?": "Could not decrypt comment; Wrong key?":
"Impossible de déchiffrer le commentaire; mauvaise clé ?", "Impossible de déchiffrer le commentaire ; mauvaise clé ?",
"Reply": "Reply":
"Répondre", "Répondre",
"Anonymous": "Anonymous":
@@ -139,8 +139,8 @@
"Cloned: '%s'": "Cloner '%s'", "Cloned: '%s'": "Cloner '%s'",
"The cloned file '%s' was attached to this paste.": "Le fichier cloné '%s' a été attaché à ce paste.", "The cloned file '%s' was attached to this paste.": "Le fichier cloné '%s' a été attaché à ce paste.",
"Attach a file": "Attacher un fichier ", "Attach a file": "Attacher un fichier ",
"alternatively drag & drop a file or paste an image from the clipboard": "alternativement, glisser & déposer un fichier ou coller une image à partir du presse-papiers", "alternatively drag & drop a file or paste an image from the clipboard": "alternatively drag & drop a file or paste an image from the clipboard",
"File too large, to display a preview. Please download the attachment.": "Fichier trop volumineux, pour afficher un aperçu. Veuillez télécharger la pièce jointe.", "File too large, to display a preview. Please download the attachment.": "File too large, to display a preview. Please download the attachment.",
"Remove attachment": "Enlever l'attachement", "Remove attachment": "Enlever l'attachement",
"Your browser does not support uploading encrypted files. Please use a newer browser.": "Your browser does not support uploading encrypted files. Please use a newer browser.":
"Votre navigateur ne supporte pas l'envoi de fichiers chiffrés. Merci d'utiliser un navigateur plus récent.", "Votre navigateur ne supporte pas l'envoi de fichiers chiffrés. Merci d'utiliser un navigateur plus récent.",
@@ -162,13 +162,13 @@
"Si ce message ne disparaîssait pas, jetez un oeil à <a href=\"%s\">cette FAQ pour des idées de résolution</a> (en Anglais).", "Si ce message ne disparaîssait pas, jetez un oeil à <a href=\"%s\">cette FAQ pour des idées de résolution</a> (en Anglais).",
"+++ no paste text +++": "+++ pas de paste-text +++", "+++ no paste text +++": "+++ pas de paste-text +++",
"Could not get paste data: %s": "Could not get paste data: %s":
"Impossible d'obtenir les données du paste: %s", "Could not get paste data: %s",
"QR code": "QR code", "QR code": "QR code",
"I love you too, bot…": "Je taime aussi, bot…", "I love you too, bot…": "I love you too, bot…",
"This website is using an insecure HTTP connection! Please use it only for testing.": "This website is using an insecure HTTP connection! Please use it only for testing.":
"Ce site web utilise une connexion HTTP non sécurisée ! Veuillez lutiliser uniquement pour des tests.", "This website is using an insecure HTTP connection! Please use it only for testing.",
"For more information <a href=\"%s\">see this FAQ entry</a>.": "For more information <a href=\"%s\">see this FAQ entry</a>.":
"Pour plus d'informations <a href=\"%s\">consultez cette rubrique de la FAQ</a>.", "For more information <a href=\"%s\">see this FAQ entry</a>.",
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
"Votre navigateur peut nécessiter une connexion HTTPS pour prendre en charge lAPI WebCrypto. Essayez <a href=\"%s\">de passer en HTTPS</a>." "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>."
} }

View File

@@ -30,7 +30,7 @@
"Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.":
"Geton de supression incorrècte. Lo tèxte es pas estat suprimit.", "Geton de supression incorrècte. Lo tèxte es pas estat suprimit.",
"Paste was properly deleted.": "Paste was properly deleted.":
"Lo tèxte es estat corrèctament suprimit.", "Lo tèxte es estat correctament suprimit.",
"JavaScript is required for %s to work.<br />Sorry for the inconvenience.": "JavaScript is required for %s to work.<br />Sorry for the inconvenience.":
"JavaScript es requesit per far foncionar %s. <br />O planhèm per linconvenient.", "JavaScript es requesit per far foncionar %s. <br />O planhèm per linconvenient.",
"%s requires a modern browser to work.": "%s requires a modern browser to work.":
@@ -109,7 +109,7 @@
"unknown status": "unknown status":
"Estatut desconegut", "Estatut desconegut",
"server error or not responding": "server error or not responding":
"Lo servidor respond pas o a rescontrat una error", "Lo servidor respond pas o a rencontrat una error",
"Could not post comment: %s": "Could not post comment: %s":
"Impossible de mandar lo comentari:%s", "Impossible de mandar lo comentari:%s",
"Sending paste…": "Sending paste…":
@@ -159,16 +159,16 @@
"Decrypting paste…": "Deschirament del tèxte…", "Decrypting paste…": "Deschirament del tèxte…",
"Preparing new paste…": "Preparacion…", "Preparing new paste…": "Preparacion…",
"In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.":
"Se per cas aqueste messatge quite pas de safichar mercés de gaitar <a href=\"%s\">aquesta FAQ per las solucions</a> (en anglés).", "Se per cas aqueste messatge quita pas de safichar mercés de gaitar <a href=\"%s\">aquesta FAQ per las solucions</a> (en anglés).",
"+++ no paste text +++": "+++ cap de tèxte pegat +++", "+++ no paste text +++": "+++ cap de tèxte pegat +++",
"Could not get paste data: %s": "Could not get paste data: %s":
"Recuperacion impossibla de las donadas copiadas: %s", "Recuperacion impossibla de las donadas copiadas: %s",
"QR code": "Còdi QR", "QR code": "Còdi QR",
"I love you too, bot…": "Taimi tanben, robòt…", "I love you too, bot…": "I love you too, bot…",
"This website is using an insecure HTTP connection! Please use it only for testing.": "This website is using an insecure HTTP connection! Please use it only for testing.":
"Aqueste site utiliza una connexion HTTP pas segura ! Mercés de lutilizar pas que per densages.", "This website is using an insecure HTTP connection! Please use it only for testing.",
"For more information <a href=\"%s\">see this FAQ entry</a>.": "For more information <a href=\"%s\">see this FAQ entry</a>.":
"Per mai dinformacions <a href=\"%s\">vejatz aqueste article de FAQ</a>.", "For more information <a href=\"%s\">see this FAQ entry</a>.",
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
"Se pòt que vòstre navigator faga besonh duna connexion HTTPS per èsser compatible amb lAPI WebCrypto. Ensajatz de <a href=\"%s\">passar al HTTPS</a>." "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>."
} }

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
// change this, if your php files and data is outside of your webservers document root // change this, if your php files and data is outside of your webservers document root

View File

@@ -1,6 +1,6 @@
{ {
"name": "privatebin", "name": "privatebin",
"version": "1.3", "version": "1.2.1",
"description": "PrivateBin is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted in the browser using 256 bit AES in Galois Counter mode (GCM).", "description": "PrivateBin is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted in the browser using 256 bit AES in Galois Counter mode (GCM).",
"main": "privatebin.js", "main": "privatebin.js",
"directories": { "directories": {

View File

@@ -26,6 +26,9 @@
}, },
"time_to_live": { "time_to_live": {
"@type": "pb:RemainingSeconds" "@type": "pb:RemainingSeconds"
},
"challenge": {
"@type": "pb:Challenge"
} }
} }
} }

View File

@@ -6,7 +6,7 @@
* @see {@link https://github.com/PrivateBin/PrivateBin} * @see {@link https://github.com/PrivateBin/PrivateBin}
* @copyright 2012 Sébastien SAUVAGE ({@link http://sebsauvage.net}) * @copyright 2012 Sébastien SAUVAGE ({@link http://sebsauvage.net})
* @license {@link https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License} * @license {@link https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License}
* @version 1.3 * @version 1.2.1
* @name PrivateBin * @name PrivateBin
* @namespace * @namespace
*/ */
@@ -851,10 +851,12 @@ jQuery.PrivateBin = (function($, RawDeflate) {
* @param {string} key * @param {string} key
* @param {string} password * @param {string} password
* @param {array} spec cryptographic specification * @param {array} spec cryptographic specification
* @param {bool} exportKey
* @return {CryptoKey} derived key * @return {CryptoKey} derived key
*/ */
async function deriveKey(key, password, spec) async function deriveKey(key, password, spec, exportKey)
{ {
exportKey = exportKey || false;
let keyArray = stringToArraybuffer(key); let keyArray = stringToArraybuffer(key);
if (password.length > 0) { if (password.length > 0) {
// version 1 pastes did append the passwords SHA-256 hash in hex // version 1 pastes did append the passwords SHA-256 hash in hex
@@ -899,11 +901,38 @@ jQuery.PrivateBin = (function($, RawDeflate) {
name: 'AES-' + spec[6].toUpperCase(), // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC") name: 'AES-' + spec[6].toUpperCase(), // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC")
length: spec[3] // can be 128, 192 or 256 length: spec[3] // can be 128, 192 or 256
}, },
false, // the key may not be exported exportKey, // may the key get exported, false by default
['encrypt', 'decrypt'] // we may only use it for en- and decryption ['encrypt', 'decrypt'] // we may only use it for en- and decryption
); );
} }
/**
* derive PBKDF2 protected credentials for server to validate password
*
* @name CryptTool.deriveCredentials
* @function
* @private
* @param {string} key
* @param {string} password
* @return {string} derived key
*/
async function deriveCredentials(key, password)
{
const spec = [
null, // initialization vector
key.slice(0, 16), // salt
100000, // iterations
256, // key size
null, // tag size
null, // algorithm
'gcm', // algorithm mode
'none' // compression
];
return window.crypto.subtle.exportKey(
'raw', await deriveKey(key.slice(16), password, spec, true)
);
}
/** /**
* gets crypto settings from specification and authenticated data * gets crypto settings from specification and authenticated data
* *
@@ -924,6 +953,56 @@ jQuery.PrivateBin = (function($, RawDeflate) {
}; };
} }
/**
* get PBKDF2 protected credentials for server to validate password
*
* @name CryptTool.getCredentials
* @function
* @param {string} key
* @param {string} password
* @return {string} derived key
*/
me.getCredentials = async function(key, password)
{
return btoa(
arraybufferToString(
await deriveCredentials(key, password)
)
);
}
/**
* get HMAC of paste ID and PBKDF2 protected credentials for server to validate
*
* @name CryptTool.getToken
* @function
* @param {string} id
* @param {string} key
* @param {string} password
* @return {string} decrypted message, empty if decryption failed
*/
me.getToken = async function(id, key, password)
{
return btoa(
arraybufferToString(
await window.crypto.subtle.sign(
{name: 'HMAC'},
await window.crypto.subtle.importKey(
'raw',
await deriveCredentials(key, password),
{
name: 'HMAC',
hash: {name: 'SHA-256'} // can be "SHA-1", "SHA-256", "SHA-384" or "SHA-512"
},
false, // may not export this
['sign']
),
stringToArraybuffer(id)
)
)
);
}
/** /**
* compress, then encrypt message with given key and password * compress, then encrypt message with given key and password
* *
@@ -1128,7 +1207,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
* force a data reload. Default: true * force a data reload. Default: true
* @return string * @return string
*/ */
me.getPasteData = function(callback, useCache) me.getPasteData = async function(callback, useCache)
{ {
// use cache if possible/allowed // use cache if possible/allowed
if (useCache !== false && pasteData !== null) { if (useCache !== false && pasteData !== null) {
@@ -1141,17 +1220,31 @@ jQuery.PrivateBin = (function($, RawDeflate) {
return pasteData; return pasteData;
} }
// reload data // load data
ServerInteraction.prepare(); ServerInteraction.prepare();
ServerInteraction.setUrl(Helper.baseUri() + '?pasteid=' + me.getPasteId()); ServerInteraction.setUrl(
Helper.baseUri() + '?' + $.param({
pasteid: me.getPasteId(),
token: await CryptTool.getToken(
me.getPasteId(), me.getPasteKey(), Prompt.getPassword()
)
})
);
ServerInteraction.setFailure(function (status, data) { ServerInteraction.setFailure(function (status, data) {
// revert loading status… // revert loading status…
Alert.hideLoading(); Alert.hideLoading();
TopNav.showViewButtons(); TopNav.showViewButtons();
// might be a missing password, try one more time after getting one
if (Prompt.getPassword().length === 0) {
Prompt.requestPassword(function () {
me.getPasteData(callback, useCache);
});
} else {
// show error message // show error message
Alert.showError(ServerInteraction.parseUploadError(status, data, 'get paste data')); Alert.showError(ServerInteraction.parseUploadError(status, data, 'get paste data'));
}
}); });
ServerInteraction.setSuccess(function (status, data) { ServerInteraction.setSuccess(function (status, data) {
pasteData = new Paste(data); pasteData = new Paste(data);
@@ -1877,8 +1970,9 @@ jQuery.PrivateBin = (function($, RawDeflate) {
* *
* @name Prompt.requestPassword * @name Prompt.requestPassword
* @function * @function
* @param {function} callback
*/ */
me.requestPassword = function() me.requestPassword = function(callback)
{ {
// show new bootstrap method (if available) // show new bootstrap method (if available)
if ($passwordModal.length !== 0) { if ($passwordModal.length !== 0) {
@@ -1896,9 +1990,9 @@ jQuery.PrivateBin = (function($, RawDeflate) {
} }
if (password.length === 0) { if (password.length === 0) {
// recurse… // recurse…
return me.requestPassword(); return me.requestPassword(callback);
} }
PasteDecrypter.run(); callback();
}; };
/** /**
@@ -4055,7 +4149,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// show notification // show notification
const baseUri = Helper.baseUri() + '?', const baseUri = Helper.baseUri() + '?',
url = baseUri + data.id + '#' + CryptTool.base58encode(data.encryptionKey), url = baseUri + data.id + '#' + CryptTool.base58encode(data.encryptionKey),
deleteUrl = baseUri + 'pasteid=' + data.id + '&deletetoken=' + data.deletetoken; deleteUrl = baseUri + $.param({pasteid: data.id, deletetoken: data.deletetoken});
PasteStatus.createPasteNotification(url, deleteUrl); PasteStatus.createPasteNotification(url, deleteUrl);
// show new URL in browser bar // show new URL in browser bar
@@ -4197,7 +4291,9 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// prepare server interaction // prepare server interaction
ServerInteraction.prepare(); ServerInteraction.prepare();
ServerInteraction.setCryptParameters(TopNav.getPassword()); const key = CryptTool.getSymmetricKey(),
password = TopNav.getPassword();
ServerInteraction.setCryptParameters(password, key);
// set success/fail functions // set success/fail functions
ServerInteraction.setSuccess(showCreatedPaste); ServerInteraction.setSuccess(showCreatedPaste);
@@ -4218,7 +4314,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
TopNav.getOpenDiscussion() ? 1 : 0, TopNav.getOpenDiscussion() ? 1 : 0,
TopNav.getBurnAfterReading() ? 1 : 0 TopNav.getBurnAfterReading() ? 1 : 0
]); ]);
ServerInteraction.setUnencryptedData('meta', {'expire': TopNav.getExpiration()}); ServerInteraction.setUnencryptedData('meta', {
'expire': TopNav.getExpiration(),
'challenge': await CryptTool.getCredentials(key, password)
});
// prepare PasteViewer for later preview // prepare PasteViewer for later preview
PasteViewer.setText(plainText); PasteViewer.setText(plainText);
@@ -4281,7 +4380,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// if it fails, request password // if it fails, request password
if (plaindata.length === 0 && password.length === 0) { if (plaindata.length === 0 && password.length === 0) {
// show prompt // show prompt
Prompt.requestPassword(); Prompt.requestPassword(me.run);
// Thus, we cannot do anything yet, we need to wait for the user // Thus, we cannot do anything yet, we need to wait for the user
// input. // input.
@@ -4727,20 +4826,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
const orgPosition = $(window).scrollTop(); const orgPosition = $(window).scrollTop();
Model.getPasteData(function (data) { Model.getPasteData(function (data) {
ServerInteraction.prepare();
ServerInteraction.setUrl(Helper.baseUri() + '?pasteid=' + Model.getPasteId());
ServerInteraction.setFailure(function (status, data) {
// revert loading status…
Alert.hideLoading();
TopNav.showViewButtons();
// show error message
Alert.showError(
ServerInteraction.parseUploadError(status, data, 'refresh display')
);
});
ServerInteraction.setSuccess(function (status, data) {
PasteDecrypter.run(new Paste(data)); PasteDecrypter.run(new Paste(data));
// restore position // restore position
@@ -4750,8 +4835,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// asyncronously if PasteDecrypter e.g. needs to wait for a // asyncronously if PasteDecrypter e.g. needs to wait for a
// password being entered // password being entered
callback(); callback();
});
ServerInteraction.run();
}, false); // this false is important as it circumvents the cache }, false); // this false is important as it circumvents the cache
} }

View File

@@ -4,9 +4,6 @@ var common = require('../common');
describe('AttachmentViewer', function () { describe('AttachmentViewer', function () {
describe('setAttachment, showAttachment, removeAttachment, hideAttachment, hideAttachmentPreview, hasAttachment, getAttachment & moveAttachmentTo', function () { describe('setAttachment, showAttachment, removeAttachment, hideAttachment, hideAttachmentPreview, hasAttachment, getAttachment & moveAttachmentTo', function () {
this.timeout(30000); this.timeout(30000);
before(function () {
cleanup();
});
jsc.property( jsc.property(
'displays & hides data as requested', 'displays & hides data as requested',

View File

@@ -237,18 +237,47 @@ conseq_or_bottom inv (interp (nth_iterate sBody n) (MemElem mem))
}); });
}); });
describe('getCredentials', function () {
it('generates credentials with password', async function () {
const clean = jsdom();
window.crypto = new WebCrypto();
// choosen by fair dice roll
const key = atob('EqueAutxlrekNNEvJWB1uaaiwbk/GGpn4++cdk+uDMc='),
// -- "That's amazing. I've got the same combination on my luggage."
password = Array.apply(0, Array(6)).map((_,b) => b + 1).join('');
const credentials = await $.PrivateBin.CryptTool.getCredentials(
key, password
);
clean();
assert.strictEqual(credentials, 'JS8bJWFx1bAPI2LMxfWrw4AQ7cedNVl8UmjUd/pW7Yg=');
});
it('generates credentials without password', async function () {
const clean = jsdom();
window.crypto = new WebCrypto();
// choosen by fair dice roll
const key = atob('U844LK1y2uUPthTgMvPECwGyQzwScCwkaEI/+qLfQSE='),
password = '';
const credentials = await $.PrivateBin.CryptTool.getCredentials(
key, password
);
clean();
assert.strictEqual(credentials, 'VfAvY7T9rm3K3JKtiOeb+B+rXnE6yZ4bYQTaD9jwjEk=');
});
});
describe('getSymmetricKey', function () { describe('getSymmetricKey', function () {
this.timeout(30000); this.timeout(30000);
var keys = []; let keys = [];
// the parameter is used to ensure the test is run more then one time // the parameter is used to ensure the test is run more then one time
jsc.property( jsc.property(
'returns random, non-empty keys', 'returns random, non-empty keys',
'integer', 'integer',
function(counter) { function(counter) {
var clean = jsdom(); const clean = jsdom();
window.crypto = new WebCrypto(); window.crypto = new WebCrypto();
var key = $.PrivateBin.CryptTool.getSymmetricKey(), const key = $.PrivateBin.CryptTool.getSymmetricKey(),
result = (key !== '' && keys.indexOf(key) === -1); result = (key !== '' && keys.indexOf(key) === -1);
keys.push(key); keys.push(key);
clean(); clean();

View File

@@ -22,7 +22,7 @@ describe('InitialCheck', function () {
'</body></html>' '</body></html>'
); );
$.PrivateBin.Alert.init(); $.PrivateBin.Alert.init();
window.crypto = null; window.crypto = new WebCrypto();
const result1 = !$.PrivateBin.InitialCheck.init(), const result1 = !$.PrivateBin.InitialCheck.init(),
result2 = !$('#errormessage').hasClass('hidden'); result2 = !$('#errormessage').hasClass('hidden');
clean(); clean();
@@ -76,7 +76,7 @@ describe('InitialCheck', function () {
'</body></html>' '</body></html>'
); );
$.PrivateBin.Alert.init(); $.PrivateBin.Alert.init();
window.crypto = null; window.crypto = new WebCrypto();
const result1 = $.PrivateBin.InitialCheck.init(), const result1 = $.PrivateBin.InitialCheck.init(),
result2 = isSecureContext === $('#httpnotice').hasClass('hidden'); result2 = isSecureContext === $('#httpnotice').hasClass('hidden');
clean(); clean();

View File

@@ -92,6 +92,9 @@
"@type": "dp:Second", "@type": "dp:Second",
"@minimum": 1 "@minimum": 1
}, },
"Challenge": {
"@type": "pb:Base64"
},
"CipherParameters": { "CipherParameters": {
"@container": "@list", "@container": "@list",
"@value": [ "@value": [

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin; namespace PrivateBin;

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin; namespace PrivateBin;
@@ -28,7 +28,7 @@ class Controller
* *
* @const string * @const string
*/ */
const VERSION = '1.3'; const VERSION = '1.2.1';
/** /**
* minimal required PHP version * minimal required PHP version
@@ -276,9 +276,7 @@ class Controller
// accessing this method ensures that the paste would be // accessing this method ensures that the paste would be
// deleted if it has already expired // deleted if it has already expired
$paste->get(); $paste->get();
if ( if ($paste->isDeleteTokenCorrect($deletetoken)) {
Filter::slowEquals($deletetoken, $paste->getDeleteToken())
) {
// Paste exists and deletion token is valid: Delete the paste. // Paste exists and deletion token is valid: Delete the paste.
$paste->delete(); $paste->delete();
$this->_status = 'Paste was properly deleted.'; $this->_status = 'Paste was properly deleted.';
@@ -315,9 +313,20 @@ class Controller
try { try {
$paste = $this->_model->getPaste($dataid); $paste = $this->_model->getPaste($dataid);
if ($paste->exists()) { if ($paste->exists()) {
// handle challenge response
if (!$paste->isTokenCorrect($this->_request->getParam('token'))) {
// we send a generic error to avoid leaking information
// about the existance of a burn after reading pastes
// this avoids an attacker being able to poll, if it has
// been read by the intended recipient or not
$this->_return_message(1, self::GENERIC_ERROR);
return;
}
$data = $paste->get(); $data = $paste->get();
if (array_key_exists('salt', $data['meta'])) { foreach (array('salt', 'challenge') as $key) {
unset($data['meta']['salt']); if (array_key_exists($key, $data['meta'])) {
unset($data['meta'][$key]);
}
} }
$this->_return_message(0, $dataid, (array) $data); $this->_return_message(0, $dataid, (array) $data);
} else { } else {

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin\Data; namespace PrivateBin\Data;

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin\Data; namespace PrivateBin\Data;

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin\Data; namespace PrivateBin\Data;

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin; namespace PrivateBin;
@@ -72,6 +72,7 @@ class Filter
/** /**
* fixed time string comparison operation to prevent timing attacks * fixed time string comparison operation to prevent timing attacks
* https://crackstation.net/hashing-security.htm?=rd#slowequals * https://crackstation.net/hashing-security.htm?=rd#slowequals
* can be replaced with hash_equals() after we drop PHP 5.5 support
* *
* @access public * @access public
* @static * @static

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin; namespace PrivateBin;
@@ -67,6 +67,13 @@ class FormatV2
if (!($ct = base64_decode($message['ct'], true))) { if (!($ct = base64_decode($message['ct'], true))) {
return false; return false;
} }
// - (optional) challenge
if (
!$isComment && array_key_exists('challenge', $message['meta']) &&
!base64_decode($message['meta']['challenge'], true)
) {
return false;
}
// Make sure some fields have a reasonable size: // Make sure some fields have a reasonable size:
// - initialization vector // - initialization vector
@@ -116,8 +123,7 @@ class FormatV2
// require only the key 'expire' in the metadata of pastes // require only the key 'expire' in the metadata of pastes
if (!$isComment && ( if (!$isComment && (
count($message['meta']) === 0 || count($message['meta']) === 0 ||
!array_key_exists('expire', $message['meta']) || !array_key_exists('expire', $message['meta'])
count($message['meta']) > 1
)) { )) {
return false; return false;
} }

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin; namespace PrivateBin;

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin; namespace PrivateBin;

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin; namespace PrivateBin;

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin\Model; namespace PrivateBin\Model;

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin\Model; namespace PrivateBin\Model;

View File

@@ -7,13 +7,14 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin\Model; namespace PrivateBin\Model;
use Exception; use Exception;
use PrivateBin\Controller; use PrivateBin\Controller;
use PrivateBin\Filter;
use PrivateBin\Persistence\ServerSalt; use PrivateBin\Persistence\ServerSalt;
/** /**
@@ -23,6 +24,14 @@ use PrivateBin\Persistence\ServerSalt;
*/ */
class Paste extends AbstractModel class Paste extends AbstractModel
{ {
/**
* Token for challenge/response.
*
* @access protected
* @var string
*/
protected $_token = '';
/** /**
* Get paste data. * Get paste data.
* *
@@ -32,6 +41,11 @@ class Paste extends AbstractModel
*/ */
public function get() public function get()
{ {
// return cached result if one is found
if (array_key_exists('adata', $this->_data) || array_key_exists('data', $this->_data)) {
return $this->_data;
}
$data = $this->_store->read($this->getId()); $data = $this->_store->read($this->getId());
if ($data === false) { if ($data === false) {
throw new Exception(Controller::GENERIC_ERROR, 64); throw new Exception(Controller::GENERIC_ERROR, 64);
@@ -48,10 +62,16 @@ class Paste extends AbstractModel
unset($data['meta']['expire_date']); unset($data['meta']['expire_date']);
} }
// check if non-expired burn after reading paste needs to be deleted // check if non-expired burn after reading paste needs to be deleted,
// but don't delete it if an incorrect token was sent
if ( if (
(
(array_key_exists('adata', $data) && $data['adata'][3] === 1) || (array_key_exists('adata', $data) && $data['adata'][3] === 1) ||
(array_key_exists('burnafterreading', $data['meta']) && $data['meta']['burnafterreading']) (array_key_exists('burnafterreading', $data['meta']) && $data['meta']['burnafterreading'])
) && (
!array_key_exists('challenge', $data['meta']) ||
$this->_token === $data['meta']['challenge']
)
) { ) {
$this->delete(); $this->delete();
} }
@@ -94,6 +114,12 @@ class Paste extends AbstractModel
$this->_data['meta']['created'] = time(); $this->_data['meta']['created'] = time();
$this->_data['meta']['salt'] = serversalt::generate(); $this->_data['meta']['salt'] = serversalt::generate();
// if a challenge was sent, we store the HMAC of paste ID & challenge
if (array_key_exists('challenge', $this->_data['meta'])) {
$this->_data['meta']['challenge'] = base64_encode(hash_hmac(
'sha256', $this->getId(), base64_decode($this->_data['meta']['challenge']), true
));
}
// store paste // store paste
if ( if (
@@ -201,6 +227,40 @@ class Paste extends AbstractModel
(array_key_exists('opendiscussion', $this->_data['meta']) && $this->_data['meta']['opendiscussion']); (array_key_exists('opendiscussion', $this->_data['meta']) && $this->_data['meta']['opendiscussion']);
} }
/**
* Check if paste challenge matches provided token.
*
* @access public
* @param string $token
* @throws Exception
* @return bool
*/
public function isTokenCorrect($token)
{
$this->_token = $token;
if (!array_key_exists('challenge', $this->_data['meta'])) {
$this->get();
}
if (array_key_exists('challenge', $this->_data['meta'])) {
return Filter::slowEquals($token, $this->_data['meta']['challenge']);
}
// paste created without challenge, accept every token sent
return true;
}
/**
* Check if paste salt based HMAC matches provided delete token.
*
* @access public
* @param string $deletetoken
* @throws Exception
* @return bool
*/
public function isDeleteTokenCorrect($deletetoken)
{
return Filter::slowEquals($deletetoken, $this->getDeleteToken());
}
/** /**
* Sanitizes data to conform with current configuration. * Sanitizes data to conform with current configuration.
* *

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin\Persistence; namespace PrivateBin\Persistence;

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin\Persistence; namespace PrivateBin\Persistence;

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin\Persistence; namespace PrivateBin\Persistence;

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin\Persistence; namespace PrivateBin\Persistence;

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin; namespace PrivateBin;

View File

@@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.3 * @version 1.2.1
*/ */
namespace PrivateBin; namespace PrivateBin;

View File

@@ -8,7 +8,7 @@
* @link http://sebsauvage.net/wiki/doku.php?id=php:vizhash_gd * @link http://sebsauvage.net/wiki/doku.php?id=php:vizhash_gd
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 0.0.5 beta PrivateBin 1.3 * @version 0.0.5 beta PrivateBin 1.2.1
*/ */
namespace PrivateBin; namespace PrivateBin;

View File

@@ -71,7 +71,7 @@ if ($MARKDOWN):
endif; endif;
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/purify-1.0.11.js" integrity="sha512-p7UyJuyBkhMcMgE4mDsgK0Lz70OvetLefua1oXs1OujWv9gOxh4xy8InFux7bZ4/DAZsTmO4rgVwZW9BHKaTaw==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/purify-1.0.11.js" integrity="sha512-p7UyJuyBkhMcMgE4mDsgK0Lz70OvetLefua1oXs1OujWv9gOxh4xy8InFux7bZ4/DAZsTmO4rgVwZW9BHKaTaw==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-dNBKGlXagUZhkKcI+HhEH0otOswA5g2QnJV8BxYxNK/piW9jJWZvADCbzDJNRjt6A6QgMwBM8+lH7K3lgsOuQw==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-r9MutKcgP/igbs8aUbENyJEie7LMyJ22f2On0RwGL0Hq0seJnmnPo4avDfhR0E/TZWDoux2arzxYHneH2/Ltmw==" crossorigin="anonymous"></script>
<!--[if IE]> <!--[if IE]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;}</style> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;}</style>
<![endif]--> <![endif]-->

View File

@@ -49,7 +49,7 @@ if ($MARKDOWN):
endif; endif;
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/purify-1.0.11.js" integrity="sha512-p7UyJuyBkhMcMgE4mDsgK0Lz70OvetLefua1oXs1OujWv9gOxh4xy8InFux7bZ4/DAZsTmO4rgVwZW9BHKaTaw==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/purify-1.0.11.js" integrity="sha512-p7UyJuyBkhMcMgE4mDsgK0Lz70OvetLefua1oXs1OujWv9gOxh4xy8InFux7bZ4/DAZsTmO4rgVwZW9BHKaTaw==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-dNBKGlXagUZhkKcI+HhEH0otOswA5g2QnJV8BxYxNK/piW9jJWZvADCbzDJNRjt6A6QgMwBM8+lH7K3lgsOuQw==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-r9MutKcgP/igbs8aUbENyJEie7LMyJ22f2On0RwGL0Hq0seJnmnPo4avDfhR0E/TZWDoux2arzxYHneH2/Ltmw==" crossorigin="anonymous"></script>
<!--[if IE]> <!--[if IE]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;}</style> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;}</style>
<![endif]--> <![endif]-->

View File

@@ -155,7 +155,7 @@ class Helper
public static function getPastePost($version = 2, array $meta = array()) public static function getPastePost($version = 2, array $meta = array())
{ {
$example = self::getPaste($version, $meta); $example = self::getPaste($version, $meta);
$example['meta'] = array('expire' => $example['meta']['expire']); $example['meta'] = array_merge(array('expire' => $example['meta']['expire']), $meta);
return $example; return $example;
} }

View File

@@ -387,7 +387,7 @@ class ConfigurationTestGenerator
} }
} }
$code .= $this->_getFunction( $code .= $this->_getFunction(
ucfirst($step), $key, $options, $preCode, $testCode, $fullOptions['main']['discussion'] ucfirst($step), $key, $options, $preCode, $testCode
); );
} }
} }
@@ -408,11 +408,10 @@ class ConfigurationTestGenerator
* DO NOT EDIT: This file is generated automatically using configGenerator.php * DO NOT EDIT: This file is generated automatically using configGenerator.php
*/ */
use PrivateBin\Controller; use PrivateBin\PrivateBin;
use PrivateBin\Data\Filesystem; use PrivateBin\Data\Filesystem;
use PrivateBin\Persistence\ServerSalt; use PrivateBin\Persistence\ServerSalt;
use PrivateBin\Persistence\TrafficLimiter; use PrivateBin\Persistence\TrafficLimiter;
use PrivateBin\Request;
class ConfigurationCombinationsTest extends PHPUnit_Framework_TestCase class ConfigurationCombinationsTest extends PHPUnit_Framework_TestCase
{ {
@@ -468,7 +467,7 @@ EOT;
* @param array $testCode * @param array $testCode
* @return string * @return string
*/ */
private function _getFunction($step, $key, &$options, $preCode, $testCode, $discussionEnabled) private function _getFunction($step, $key, &$options, $preCode, $testCode)
{ {
if (count($testCode) == 0) { if (count($testCode) == 0) {
echo "skipping creation of test$step$key, no valid tests found for configuration: $options" . PHP_EOL; echo "skipping creation of test$step$key, no valid tests found for configuration: $options" . PHP_EOL;
@@ -496,21 +495,8 @@ EOT;
// step specific initialization // step specific initialization
switch ($step) { switch ($step) {
case 'Create': case 'Create':
if ($discussionEnabled) {
$code .= PHP_EOL . <<<'EOT' $code .= PHP_EOL . <<<'EOT'
$paste = Helper::getPasteJson(); $_POST = Helper::getPaste();
EOT;
} else {
$code .= PHP_EOL . <<<'EOT'
$paste = json_decode(Helper::getPasteJson(), true);
$paste['adata'][2] = 0;
$paste = json_encode($paste);
EOT;
}
$code .= PHP_EOL . <<<'EOT'
$file = tempnam(sys_get_temp_dir(), 'FOO');
file_put_contents($file, $paste);
Request::setInputStream($file);
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest'; $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
$_SERVER['REQUEST_METHOD'] = 'POST'; $_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['REMOTE_ADDR'] = '::1'; $_SERVER['REMOTE_ADDR'] = '::1';
@@ -539,7 +525,7 @@ EOT;
$code .= PHP_EOL . $preString; $code .= PHP_EOL . $preString;
$code .= <<<'EOT' $code .= <<<'EOT'
ob_start(); ob_start();
new Controller; new PrivateBin;
$content = ob_get_contents(); $content = ob_get_contents();
ob_end_clean(); ob_end_clean();
EOT; EOT;

View File

@@ -366,6 +366,30 @@ class ControllerTest extends PHPUnit_Framework_TestCase
$this->assertEquals(1, $paste['adata'][2], 'discussion is enabled'); $this->assertEquals(1, $paste['adata'][2], 'discussion is enabled');
} }
/**
* @runInSeparateProcess
*/
public function testCreateInvalidFormat()
{
$options = parse_ini_file(CONF, true);
$options['traffic']['limit'] = 0;
Helper::createIniFile(CONF, $options);
$paste = Helper::getPasteJson(2, array('challenge' => '$'));
$file = tempnam(sys_get_temp_dir(), 'FOO');
file_put_contents($file, $paste);
Request::setInputStream($file);
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['REMOTE_ADDR'] = '::1';
ob_start();
new Controller;
$content = ob_get_contents();
ob_end_clean();
$response = json_decode($content, true);
$this->assertEquals(1, $response['status'], 'outputs error status');
$this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste exists after posting data');
}
/** /**
* @runInSeparateProcess * @runInSeparateProcess
*/ */
@@ -784,6 +808,56 @@ class ControllerTest extends PHPUnit_Framework_TestCase
$this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste successfully deleted'); $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste successfully deleted');
} }
/**
* @runInSeparateProcess
*/
public function testReadBurnAfterReadingWithToken()
{
$token = base64_encode(hash_hmac(
'sha256', Helper::getPasteId(), random_bytes(32), true
));
$burnPaste = Helper::getPaste(2, array('challenge' => $token));
$burnPaste['adata'][3] = 1;
$this->_data->create(Helper::getPasteId(), $burnPaste);
$this->assertTrue($this->_data->exists(Helper::getPasteId()), 'paste exists before deleting data');
$_SERVER['QUERY_STRING'] = Helper::getPasteId() . '&token=' . $token;
$_GET[Helper::getPasteId()] = '';
$_GET['token'] = $token;
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
ob_start();
new Controller;
$content = ob_get_contents();
ob_end_clean();
$response = json_decode($content, true);
$this->assertEquals(0, $response['status'], 'outputs status');
$this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste successfully deleted');
}
/**
* @runInSeparateProcess
*/
public function testReadBurnAfterReadingWithIncorrectToken()
{
$token = base64_encode(hash_hmac(
'sha256', Helper::getPasteId(), random_bytes(32), true
));
$burnPaste = Helper::getPaste(2, array('challenge' => base64_encode(random_bytes(32))));
$burnPaste['adata'][3] = 1;
$this->_data->create(Helper::getPasteId(), $burnPaste);
$this->assertTrue($this->_data->exists(Helper::getPasteId()), 'paste exists before deleting data');
$_SERVER['QUERY_STRING'] = Helper::getPasteId() . '&token=' . $token;
$_GET[Helper::getPasteId()] = '';
$_GET['token'] = $token;
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
ob_start();
new Controller;
$content = ob_get_contents();
ob_end_clean();
$response = json_decode($content, true);
$this->assertEquals(1, $response['status'], 'outputs status');
$this->assertTrue($this->_data->exists(Helper::getPasteId()), 'paste not deleted');
}
/** /**
* @runInSeparateProcess * @runInSeparateProcess
*/ */

View File

@@ -21,6 +21,10 @@ class FormatV2Test extends PHPUnit_Framework_TestCase
$paste['ct'] = '$'; $paste['ct'] = '$';
$this->assertFalse(FormatV2::isValid($paste), 'invalid base64 encoding of ct'); $this->assertFalse(FormatV2::isValid($paste), 'invalid base64 encoding of ct');
$paste = Helper::getPastePost();
$paste['meta']['challenge'] = '$';
$this->assertFalse(FormatV2::isValid($paste), 'invalid base64 encoding of ct');
$paste = Helper::getPastePost(); $paste = Helper::getPastePost();
$paste['ct'] = 'bm9kYXRhbm9kYXRhbm9kYXRhbm9kYXRhbm9kYXRhCg=='; $paste['ct'] = 'bm9kYXRhbm9kYXRhbm9kYXRhbm9kYXRhbm9kYXRhCg==';
$this->assertFalse(FormatV2::isValid($paste), 'low ct entropy'); $this->assertFalse(FormatV2::isValid($paste), 'low ct entropy');
@@ -67,6 +71,8 @@ class FormatV2Test extends PHPUnit_Framework_TestCase
$paste['adata'][0][7] = '!#@'; $paste['adata'][0][7] = '!#@';
$this->assertFalse(FormatV2::isValid($paste), 'invalid compression'); $this->assertFalse(FormatV2::isValid($paste), 'invalid compression');
$this->assertFalse(FormatV2::isValid(Helper::getPaste()), 'invalid meta key'); $paste = Helper::getPastePost();
unset($paste['meta']['expire']);
$this->assertFalse(FormatV2::isValid($paste), 'invalid missing meta key');
} }
} }

View File

@@ -268,10 +268,48 @@ class ModelTest extends PHPUnit_Framework_TestCase
$paste->setData($pasteData); $paste->setData($pasteData);
$paste->store(); $paste->store();
$paste = $paste->get(); $paste = $this->_model->getPaste(Helper::getPasteId())->get();
$this->assertEquals((float) 300, (float) $paste['meta']['time_to_live'], 'remaining time is set correctly', 1.0); $this->assertEquals((float) 300, (float) $paste['meta']['time_to_live'], 'remaining time is set correctly', 1.0);
} }
public function testToken()
{
$pasteData = Helper::getPastePost();
$pasteData['meta']['challenge'] = base64_encode(random_bytes(32));
$token = base64_encode(hash_hmac(
'sha256', Helper::getPasteId(), base64_decode($pasteData['meta']['challenge']), true
));
$this->_model->getPaste(Helper::getPasteId())->delete();
$paste = $this->_model->getPaste(Helper::getPasteId());
$this->assertFalse($paste->exists(), 'paste does not yet exist');
$paste = $this->_model->getPaste();
$paste->setData($pasteData);
$paste->store();
$paste = $this->_model->getPaste(Helper::getPasteId());
$this->assertTrue(
$paste->isTokenCorrect($token),
'token is accepted after store and retrieval'
);
}
public function testDeleteToken()
{
$pasteData = Helper::getPastePost();
$this->_model->getPaste(Helper::getPasteId())->delete();
$paste = $this->_model->getPaste(Helper::getPasteId());
$this->assertFalse($paste->exists(), 'paste does not yet exist');
$paste = $this->_model->getPaste();
$paste->setData($pasteData);
$paste->store();
$deletetoken = $paste->getDeleteToken();
$paste = $this->_model->getPaste(Helper::getPasteId());
$this->assertTrue($paste->isDeleteTokenCorrect($deletetoken), 'delete token is accepted after store and retrieval');
}
/** /**
* @expectedException Exception * @expectedException Exception
* @expectedExceptionCode 64 * @expectedExceptionCode 64
@@ -287,6 +325,20 @@ class ModelTest extends PHPUnit_Framework_TestCase
$paste->getComment(Helper::getPasteId())->delete(); $paste->getComment(Helper::getPasteId())->delete();
} }
/**
* @expectedException Exception
* @expectedExceptionCode 75
*/
public function testInvalidFormat()
{
$pasteData = Helper::getPastePost();
$pasteData['adata'][1] = 'foo';
$this->_model->getPaste(Helper::getPasteId())->delete();
$paste = $this->_model->getPaste();
$paste->setData($pasteData);
}
public function testPurge() public function testPurge()
{ {
$conf = new Configuration; $conf = new Configuration;

View File

@@ -130,6 +130,22 @@ class RequestTest extends PHPUnit_Framework_TestCase
$this->assertEquals('read', $request->getOperation()); $this->assertEquals('read', $request->getOperation());
} }
public function testApiReadWithToken()
{
$this->reset();
$id = $this->getRandomId();
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['HTTP_ACCEPT'] = 'application/json, text/javascript, */*; q=0.01';
$_SERVER['QUERY_STRING'] = $id . '&token=foo';
$_GET[$id] = '';
$_GET['token'] = 'foo';
$request = new Request;
$this->assertTrue($request->isJsonApiCall(), 'is JSON Api call');
$this->assertEquals($id, $request->getParam('pasteid'));
$this->assertEquals('foo', $request->getParam('token'));
$this->assertEquals('read', $request->getOperation());
}
public function testApiDelete() public function testApiDelete()
{ {
$this->reset(); $this->reset();