Added encryption based on Libsodium

This commit is contained in:
Chris
2020-01-07 19:05:22 +01:00
parent 6cab15f8e7
commit 021fbad811
11 changed files with 196 additions and 44 deletions

View File

@@ -36,6 +36,8 @@ Table of contents
## New Features in v2
- Added support for external storage
- [Encryption of files in external storage](/rtfm/ENCRYPTION.md)
- Added text hosting (like pastebin)
- Added URL shortening
- Added WebP to images (and conversion from jpg,png to webp)

View File

@@ -78,13 +78,7 @@ if($_REQUEST['base64'])
$answer['delete_url'] = URL.'delete_'.getDeleteCodeOfHash($answer['hash']).'/'.$answer['hash'];
}
// Lets' check all storage controllers and tell them that a new file was uploaded
$sc = getStorageControllers();
foreach($sc as $contr)
{
if((new $contr())->isEnabled()===true)
(new $contr())->pushFile($answer['hash']);
}
storageControllerUpload($answer['hash']);
}
echo json_encode($answer);

View File

@@ -78,13 +78,7 @@ if($answer['hash'] && $answer['status']=='ok')
}
// Lets' check all storage controllers and tell them that a new file was uploaded
$sc = getStorageControllers();
foreach($sc as $contr)
{
if((new $contr())->isEnabled()===true)
(new $contr())->pushFile($answer['hash']);
}
storageControllerUpload($answer['hash']);
}
if($answer['hash'] && $answer['status']=='ok')
@@ -98,13 +92,7 @@ if($answer['hash'] && $answer['status']=='ok')
$answer['delete_url'] = URL.'delete_'.getDeleteCodeOfHash($answer['hash']).'/'.$answer['hash'];
}
// Lets' check all storage controllers and tell them that a new file was uploaded
$sc = getStorageControllers();
foreach($sc as $contr)
{
if((new $contr())->isEnabled()===true)
(new $contr())->pushFile($answer['hash']);
}
storageControllerUpload($answer['hash']);
}
echo json_encode($answer);

View File

@@ -73,13 +73,7 @@ if ($_FILES['file']["error"] == UPLOAD_ERR_OK)
}
// Lets' check all storage controllers and tell them that a new file was uploaded
$sc = getStorageControllers();
foreach($sc as $contr)
{
if((new $contr())->isEnabled()===true)
(new $contr())->pushFile($answer['hash']);
}
storageControllerUpload($answer['hash']);
}
echo json_encode($answer);

View File

@@ -51,11 +51,27 @@ function architect($url)
foreach($sc as $contr)
{
$c = new $contr();
if($c->isEnabled()===true && $c->hashExists($el))
if($c->isEnabled()===true && $c->hashExists($el))
{
$c->pullFile($el);
$hash = $el;
break; // we brake here because we already have the file. no need to check other storage controllers
$c->pullFile($hash,ROOT.DS.'tmp'.DS.$hash);
storeFile(ROOT.DS.'tmp'.DS.$hash,$hash,true);
break; // we break here because we already have the file. no need to check other storage controllers
}
else if($c->isEnabled()===true && defined('ENCRYPTION_KEY') && ENCRYPTION_KEY !='' && $c->hashExists($el.'.enc')) //this is an encrypted file. Let's decrypt it
{
$hash = $el.'.enc';
$c->pullFile($hash,ROOT.DS.'tmp'.DS.$hash);
$enc = new Encryption;
$hash = substr($hash,0,-4);
$enc->decryptFile(ROOT.DS.'tmp'.DS.$el.'.enc', ROOT.DS.'tmp'.DS.$hash,base64_decode(ENCRYPTION_KEY));
storeFile(ROOT.DS.'tmp'.DS.$hash,$hash,true);
unlink(ROOT.DS.'tmp'.DS.$el.'.enc');
break; // we break here because we already have the file. no need to check other storage controllers
}
}
}
@@ -123,6 +139,30 @@ function architect($url)
//var_dump($u);
}
function storageControllerUpload($hash)
{
// Lets' check all storage controllers and tell them that a new file was uploaded
$sc = getStorageControllers();
foreach($sc as $contr)
{
if((new $contr())->isEnabled()===true)
{
$source = ROOT.DS.'data'.DS.$hash.DS.$hash;
if(defined('ENCRYPTION_KEY') && ENCRYPTION_KEY) //ok so we got an encryption key which means we'll store only the encrypted file
{
$enc = new Encryption;
$encoded_file = ROOT.DS.'tmp'.DS.$hash.'.enc';
$enc->encryptFile($source,$encoded_file,base64_decode(ENCRYPTION_KEY));
(new $contr())->pushFile($encoded_file,$hash.'.enc');
}
else // not encrypted
(new $contr())->pushFile($source,$hash);
}
}
}
function getNewHash($type,$length=10)
{
while(1)
@@ -155,7 +195,9 @@ function autoload($className)
if (file_exists(ROOT . DS . 'content-controllers' . DS . strtolower($className) . '.php'))
require_once(ROOT . DS . 'content-controllers' . DS . strtolower($className) . '.php');
if (file_exists(ROOT . DS . 'interfaces' . DS . strtolower($className) . '.interface.php'))
require_once(ROOT . DS . 'interfaces' . DS . strtolower($className) . '.interface.php');
require_once(ROOT . DS . 'interfaces' . DS . strtolower($className) . '.interface.php');
if ($className=='Encryption')
require_once(ROOT . DS . 'inc' . DS . 'encryption.php');
}
function renderTemplate($template,$vars=false)

85
inc/encryption.php Normal file
View File

@@ -0,0 +1,85 @@
<?php
class Encryption{
/**
* $key must have been generated at some point with: random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES)
*/
function encryptText($text,$key)
{
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = sodium_crypto_secretbox($text, $nonce, $key);
$encoded = base64_encode($nonce . $ciphertext);
return $encoded;
}
/**
* $key must have been generated at some point with: random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES)
*/
function decryptText($encoded,$key)
{
$decoded = base64_decode($encoded);
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
return $plaintext;
}
/**
* $key must have been generated at some point with: sodium_crypto_secretstream_xchacha20poly1305_keygen();
*/
function encryptFile($infile,$enc_outfile,$key)
{
$chunk_size = 4096;
$fd_in = fopen($infile, 'rb');
$fd_out = fopen($enc_outfile, 'wb');
list($stream, $header) = sodium_crypto_secretstream_xchacha20poly1305_init_push($key);
fwrite($fd_out, $header);
$tag = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE;
do {
$chunk = fread($fd_in, $chunk_size);
if (feof($fd_in)) {
$tag = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL;
}
$encrypted_chunk = sodium_crypto_secretstream_xchacha20poly1305_push($stream, $chunk, '', $tag);
fwrite($fd_out, $encrypted_chunk);
} while ($tag !== SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL);
fclose($fd_out);
fclose($fd_in);
}
/**
* $key must have been generated at some point with: sodium_crypto_secretstream_xchacha20poly1305_keygen();
*/
function decryptFile($enc_infile,$outfile,$key)
{
$fd_in = fopen($enc_infile, 'rb');
$fd_out = fopen($outfile, 'wb');
$chunk_size = 4096;
$header = fread($fd_in, SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES);
$stream = sodium_crypto_secretstream_xchacha20poly1305_init_pull($header, $key);
do {
$chunk = fread($fd_in, $chunk_size + SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
list($decrypted_chunk, $tag) = sodium_crypto_secretstream_xchacha20poly1305_pull($stream, $chunk);
fwrite($fd_out, $decrypted_chunk);
} while (!feof($fd_in) && $tag !== SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL);
$ok = feof($fd_in);
fclose($fd_out);
fclose($fd_in);
if (!$ok) {
die('Invalid/corrupted input');
}
}
}

View File

@@ -34,10 +34,11 @@ interface StorageController
* a folder that you might have to create first before putting the file in
*
* @param string $hash is the hash of the file that should be pulled from this storage system
* @param string $location is the location where the downloaded file should be placed
*
* @return bool true if successful
*/
function pullFile($hash);
function pullFile($hash,$location);
/**
* Whenever a new file is uploaded this method will be called
@@ -47,7 +48,7 @@ interface StorageController
*
* @return bool true if successful
*/
function pushFile($hash);
function pushFile($source,$hash);
/**
* If deletion of a file is requested, this method is called

View File

@@ -22,6 +22,7 @@ PictShare has an extention system that allows handling of multiple storage solut
|Option | value type | What it does|
|--- | --- | ---|
|ENCRYPTION_KEY | base64 string | The key used to encrypt/decrypt files stored in storage controllers. See [/rtfm/ENCRYPTION.md] for setup guide |
| ALT_FOLDER | string | All uploaded files will be copied to this location. This location can be a mounted network share (eg NFS or FTP, etc). If a file is not found in the normal upload direcotry, ALT_FOLDER will be checked. [more info about scaling PictShare](/rtfm/SCALING.md) |
|S3_BUCKET | string | Name of your [S3 bucket](https://aws.amazon.com/s3/) |
|S3_ACCESS_KEY | string | Access key for your bucket|

41
rtfm/ENCRYPTION.md Normal file
View File

@@ -0,0 +1,41 @@
# Encryption
As of Jan. 2020 you can set up an encryption key in your config which will encrypt all images stored on [external storage](/rtfm/CONFIG.md#storage-controllers)
The files on the PictShare server are not encrypted, only the ones on external storage providers, eg if you want to use S3 as storage.
## Dependencies
To be able to use encryption you'll need the following extensions:
- mb-string (`apt-get install php-mbstring`)
- libsodium (`apt-get install php-libsodium`)
Since only files on [storage controllers](/rtfm/CONFIG.md#storage-controllers) are encrypted, you'll need to configure at least one.
## Preparation
First you'll need to generate a key and encode it in base64.
The easiest way to get it would be to run this command:
`php -r "echo base64_encode(sodium_crypto_secretstream_xchacha20poly1305_keygen());"`
This will output something like `SSdoJvp10ZvOY5v+vAcprxQKjNX1AzD52cAnFwr6yXc=`
Now put this output in your /inc/config.inc.php like this:
`define('ENCRYPTION_KEY','SSdoJvp10ZvOY5v+vAcprxQKjNX1AzD52cAnFwr6yXc=');`
**Warning: If you change or lose the ENCRYPTION_KEY, all encrypted data will be unrecoverably lost**
# How it works
If you have everything running you can upload a new image and it will get encrypted and uploaded to your storage container(s). This means you could even host on untrusted servers/buckets since nobody without the key will be able to decrypt it.
If you have uploaded a few files and see them on your storage container (eg S3) you'll notice the file has the '.enc' extension.
When you now wipe your PictShare instances local data folder and request the file again via the URL, the storage controller will pull the encrypted file, decrypt it and save it locally (unencrypted)
# Todo
- Automatically encrypt all existing (unencrypted) files on the storage controllers

View File

@@ -13,23 +13,26 @@ class AltfolderStorage implements StorageController
return file_exists($altname);
}
function pullFile($hash)
function pullFile($hash,$location)
{
$altname=ALT_FOLDER.DS.$hash;
if(file_exists($altname))
{
storeFile($altname,$hash,false);
copy($altname,$location);
}
}
function pushFile($hash)
function pushFile($source,$hash)
{
$altname=ALT_FOLDER.DS.$hash;
$orig = ROOT.DS.'data'.DS.$hash.DS.$hash;
if(file_exists($orig) && !$this->hashExists($hash))
{
copy($orig,$altname);
}
copy($source,$altname);
return true;
}
return false;
}
function deleteFile($hash)

View File

@@ -9,7 +9,7 @@
* (optional) S3_ENDPOINT
*/
class S3Storage //implements StorageController
class S3Storage implements StorageController
{
private $s3;
function connect(){
@@ -38,27 +38,28 @@ class S3Storage //implements StorageController
return $this->s3->doesObjectExist(S3_BUCKET,$hash);
}
function pullFile($hash)
function pullFile($hash,$location)
{
if(!$this->s3)$this->connect();
if(!$this->hashExists($hash)) return false;
$this->s3->getObject([
'Bucket' => S3_BUCKET,
'Key' => $hash,
'SaveAs' => ROOT.DS.'data'.DS.$hash.DS.$hash
'SaveAs' => $location
]);
return true;
}
function pushFile($hash)
function pushFile($source,$hash)
{
if(!$this->s3)$this->connect();
$this->s3->putObject([
'Bucket' => S3_BUCKET,
'Key' => $hash,
'SourceFile' => ROOT.DS.'data'.DS.$hash.DS.$hash
'SourceFile' => $source
]);
return true;