sign all encrypted blocks and check signature on decrypt
This commit is contained in:
parent
db8f267647
commit
40a5ba72fc
|
@ -131,7 +131,8 @@ class Application extends \OCP\AppFramework\App {
|
||||||
$server = $c->getServer();
|
$server = $c->getServer();
|
||||||
return new Crypt($server->getLogger(),
|
return new Crypt($server->getLogger(),
|
||||||
$server->getUserSession(),
|
$server->getUserSession(),
|
||||||
$server->getConfig());
|
$server->getConfig(),
|
||||||
|
$server->getL10N($c->getAppName()));
|
||||||
});
|
});
|
||||||
|
|
||||||
$container->registerService('Session',
|
$container->registerService('Session',
|
||||||
|
|
|
@ -25,11 +25,12 @@ use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
$userManager = OC::$server->getUserManager();
|
$userManager = OC::$server->getUserManager();
|
||||||
$view = new \OC\Files\View();
|
$view = new \OC\Files\View();
|
||||||
$config = \OC::$server->getConfig();
|
$config = \OC::$server->getConfig();
|
||||||
|
$l = \OC::$server->getL10N('encryption');
|
||||||
$userSession = \OC::$server->getUserSession();
|
$userSession = \OC::$server->getUserSession();
|
||||||
$connection = \OC::$server->getDatabaseConnection();
|
$connection = \OC::$server->getDatabaseConnection();
|
||||||
$logger = \OC::$server->getLogger();
|
$logger = \OC::$server->getLogger();
|
||||||
$questionHelper = new QuestionHelper();
|
$questionHelper = new QuestionHelper();
|
||||||
$crypt = new \OCA\Encryption\Crypto\Crypt($logger, $userSession, $config);
|
$crypt = new \OCA\Encryption\Crypto\Crypt($logger, $userSession, $config, $l);
|
||||||
$util = new \OCA\Encryption\Util($view, $crypt, $logger, $userSession, $config, $userManager);
|
$util = new \OCA\Encryption\Util($view, $crypt, $logger, $userSession, $config, $userManager);
|
||||||
|
|
||||||
$application->add(new MigrateKeys($userManager, $view, $connection, $config, $logger));
|
$application->add(new MigrateKeys($userManager, $view, $connection, $config, $logger));
|
||||||
|
|
|
@ -29,10 +29,12 @@ namespace OCA\Encryption\Crypto;
|
||||||
|
|
||||||
use OC\Encryption\Exceptions\DecryptionFailedException;
|
use OC\Encryption\Exceptions\DecryptionFailedException;
|
||||||
use OC\Encryption\Exceptions\EncryptionFailedException;
|
use OC\Encryption\Exceptions\EncryptionFailedException;
|
||||||
|
use OC\HintException;
|
||||||
use OCA\Encryption\Exceptions\MultiKeyDecryptException;
|
use OCA\Encryption\Exceptions\MultiKeyDecryptException;
|
||||||
use OCA\Encryption\Exceptions\MultiKeyEncryptException;
|
use OCA\Encryption\Exceptions\MultiKeyEncryptException;
|
||||||
use OCP\Encryption\Exceptions\GenericEncryptionException;
|
use OCP\Encryption\Exceptions\GenericEncryptionException;
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
|
use OCP\IL10N;
|
||||||
use OCP\ILogger;
|
use OCP\ILogger;
|
||||||
use OCP\IUserSession;
|
use OCP\IUserSession;
|
||||||
|
|
||||||
|
@ -60,14 +62,22 @@ class Crypt {
|
||||||
|
|
||||||
const HEADER_START = 'HBEGIN';
|
const HEADER_START = 'HBEGIN';
|
||||||
const HEADER_END = 'HEND';
|
const HEADER_END = 'HEND';
|
||||||
|
|
||||||
/** @var ILogger */
|
/** @var ILogger */
|
||||||
private $logger;
|
private $logger;
|
||||||
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
private $user;
|
private $user;
|
||||||
|
|
||||||
/** @var IConfig */
|
/** @var IConfig */
|
||||||
private $config;
|
private $config;
|
||||||
|
|
||||||
/** @var array */
|
/** @var array */
|
||||||
private $supportedKeyFormats;
|
private $supportedKeyFormats;
|
||||||
|
|
||||||
|
/** @var IL10N */
|
||||||
|
private $l;
|
||||||
|
|
||||||
/** @var array */
|
/** @var array */
|
||||||
private $supportedCiphersAndKeySize = [
|
private $supportedCiphersAndKeySize = [
|
||||||
'AES-256-CTR' => 32,
|
'AES-256-CTR' => 32,
|
||||||
|
@ -80,11 +90,13 @@ class Crypt {
|
||||||
* @param ILogger $logger
|
* @param ILogger $logger
|
||||||
* @param IUserSession $userSession
|
* @param IUserSession $userSession
|
||||||
* @param IConfig $config
|
* @param IConfig $config
|
||||||
|
* @param IL10N $l
|
||||||
*/
|
*/
|
||||||
public function __construct(ILogger $logger, IUserSession $userSession, IConfig $config) {
|
public function __construct(ILogger $logger, IUserSession $userSession, IConfig $config, IL10N $l) {
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : '"no user given"';
|
$this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : '"no user given"';
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
|
$this->l = $l;
|
||||||
$this->supportedKeyFormats = ['hash', 'password'];
|
$this->supportedKeyFormats = ['hash', 'password'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,8 +184,12 @@ class Crypt {
|
||||||
$iv,
|
$iv,
|
||||||
$passPhrase,
|
$passPhrase,
|
||||||
$this->getCipher());
|
$this->getCipher());
|
||||||
|
|
||||||
|
$sig = $this->createSignature($encryptedContent, $passPhrase);
|
||||||
|
|
||||||
// combine content to encrypt the IV identifier and actual IV
|
// combine content to encrypt the IV identifier and actual IV
|
||||||
$catFile = $this->concatIV($encryptedContent, $iv);
|
$catFile = $this->concatIV($encryptedContent, $iv);
|
||||||
|
$catFile = $this->concatSig($catFile, $sig);
|
||||||
$padded = $this->addPadding($catFile);
|
$padded = $this->addPadding($catFile);
|
||||||
|
|
||||||
return $padded;
|
return $padded;
|
||||||
|
@ -287,6 +303,15 @@ class Crypt {
|
||||||
return $encryptedContent . '00iv00' . $iv;
|
return $encryptedContent . '00iv00' . $iv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $encryptedContent
|
||||||
|
* @param string $signature
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function concatSig($encryptedContent, $signature) {
|
||||||
|
return $encryptedContent . '00sig00' . $signature;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note: This is _NOT_ a padding used for encryption purposes. It is solely
|
* Note: This is _NOT_ a padding used for encryption purposes. It is solely
|
||||||
* used to achieve the PHP stream size. It has _NOTHING_ to do with the
|
* used to achieve the PHP stream size. It has _NOTHING_ to do with the
|
||||||
|
@ -296,7 +321,7 @@ class Crypt {
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
private function addPadding($data) {
|
private function addPadding($data) {
|
||||||
return $data . 'xx';
|
return $data . 'xxx';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -414,10 +439,12 @@ class Crypt {
|
||||||
* @throws DecryptionFailedException
|
* @throws DecryptionFailedException
|
||||||
*/
|
*/
|
||||||
public function symmetricDecryptFileContent($keyFileContents, $passPhrase, $cipher = self::DEFAULT_CIPHER) {
|
public function symmetricDecryptFileContent($keyFileContents, $passPhrase, $cipher = self::DEFAULT_CIPHER) {
|
||||||
// Remove Padding
|
|
||||||
$noPadding = $this->removePadding($keyFileContents);
|
|
||||||
|
|
||||||
$catFile = $this->splitIv($noPadding);
|
$catFile = $this->splitMetaData($keyFileContents, $cipher);
|
||||||
|
|
||||||
|
if ($catFile['signature']) {
|
||||||
|
$this->checkSignature($catFile['encrypted'], $passPhrase, $catFile['signature']);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->decrypt($catFile['encrypted'],
|
return $this->decrypt($catFile['encrypted'],
|
||||||
$catFile['iv'],
|
$catFile['iv'],
|
||||||
|
@ -425,42 +452,102 @@ class Crypt {
|
||||||
$cipher);
|
$cipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check for valid signature
|
||||||
|
*
|
||||||
|
* @param string $data
|
||||||
|
* @param string $passPhrase
|
||||||
|
* @param string $expectedSignature
|
||||||
|
* @throws HintException
|
||||||
|
*/
|
||||||
|
private function checkSignature($data, $passPhrase, $expectedSignature) {
|
||||||
|
$signature = $this->createSignature($data, $passPhrase);
|
||||||
|
if (hash_equals($expectedSignature, $signature)) {
|
||||||
|
throw new HintException('Bad Signature', $this->l->t('Bad Signature'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create signature
|
||||||
|
*
|
||||||
|
* @param string $data
|
||||||
|
* @param string $passPhrase
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function createSignature($data, $passPhrase) {
|
||||||
|
$signature = hash_hmac('sha256', $data, $passPhrase);
|
||||||
|
return $signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* remove padding
|
* remove padding
|
||||||
*
|
*
|
||||||
* @param $padded
|
* @param string $padded
|
||||||
|
* @param bool $hasSignature did the block contain a signature, in this case we use a different padding
|
||||||
* @return string|false
|
* @return string|false
|
||||||
*/
|
*/
|
||||||
private function removePadding($padded) {
|
private function removePadding($padded, $hasSignature = false) {
|
||||||
if (substr($padded, -2) === 'xx') {
|
if ($hasSignature === false && substr($padded, -2) === 'xx') {
|
||||||
return substr($padded, 0, -2);
|
return substr($padded, 0, -2);
|
||||||
|
} elseif ($hasSignature === true && substr($padded, -3) === 'xxx') {
|
||||||
|
return substr($padded, 0, -3);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* split iv from encrypted content
|
* split meta data from encrypted file
|
||||||
|
* Note: for now, we assume that the meta data always start with the iv
|
||||||
|
* followed by the signature, if available
|
||||||
*
|
*
|
||||||
* @param string|false $catFile
|
* @param string $catFile
|
||||||
* @return string
|
* @param string $cipher
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function splitIv($catFile) {
|
private function splitMetaData($catFile, $cipher) {
|
||||||
// Fetch encryption metadata from end of file
|
if ($this->hasSignature($catFile, $cipher)) {
|
||||||
$meta = substr($catFile, -22);
|
$catFile = $this->removePadding($catFile, true);
|
||||||
|
$meta = substr($catFile, -93);
|
||||||
// Fetch IV from end of file
|
$iv = substr($meta, strlen('00iv00'), 16);
|
||||||
$iv = substr($meta, -16);
|
$sig = substr($meta, 22 + strlen('00sig00'));
|
||||||
|
$encrypted = substr($catFile, 0, -93);
|
||||||
// Remove IV and IV Identifier text to expose encrypted content
|
} else {
|
||||||
|
$catFile = $this->removePadding($catFile);
|
||||||
$encrypted = substr($catFile, 0, -22);
|
$meta = substr($catFile, -22);
|
||||||
|
$iv = substr($meta, -16);
|
||||||
|
$sig = false;
|
||||||
|
$encrypted = substr($catFile, 0, -93);
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'encrypted' => $encrypted,
|
'encrypted' => $encrypted,
|
||||||
'iv' => $iv
|
'iv' => $iv,
|
||||||
|
'signature' => $sig
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check if encrypted block is signed
|
||||||
|
*
|
||||||
|
* @param string $catFile
|
||||||
|
* @param string $cipher
|
||||||
|
* @return bool
|
||||||
|
* @throws HintException
|
||||||
|
*/
|
||||||
|
private function hasSignature($catFile, $cipher) {
|
||||||
|
$meta = substr($catFile, 93);
|
||||||
|
$signaturePosition = strpos($meta, '00sig00');
|
||||||
|
|
||||||
|
// enforce signature for the new 'CTR' ciphers
|
||||||
|
if ($signaturePosition === false && strpos(strtolower($cipher), 'ctr') !== false) {
|
||||||
|
throw new HintException('Missing Signature', $this->l->t('Missing Signature'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($signaturePosition !== false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $encryptedContent
|
* @param string $encryptedContent
|
||||||
* @param string $iv
|
* @param string $iv
|
||||||
|
|
|
@ -94,6 +94,9 @@ class Encryption implements IEncryptionModule {
|
||||||
/** @var DecryptAll */
|
/** @var DecryptAll */
|
||||||
private $decryptAll;
|
private $decryptAll;
|
||||||
|
|
||||||
|
/** @var int unencrypted block size */
|
||||||
|
private $unencryptedBlockSize = 6072;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param Crypt $crypt
|
* @param Crypt $crypt
|
||||||
|
@ -253,7 +256,7 @@ class Encryption implements IEncryptionModule {
|
||||||
public function encrypt($data) {
|
public function encrypt($data) {
|
||||||
|
|
||||||
// If extra data is left over from the last round, make sure it
|
// If extra data is left over from the last round, make sure it
|
||||||
// is integrated into the next 6126 / 8192 block
|
// is integrated into the next block
|
||||||
if ($this->writeCache) {
|
if ($this->writeCache) {
|
||||||
|
|
||||||
// Concat writeCache to start of $data
|
// Concat writeCache to start of $data
|
||||||
|
@ -275,7 +278,7 @@ class Encryption implements IEncryptionModule {
|
||||||
|
|
||||||
// If data remaining to be written is less than the
|
// If data remaining to be written is less than the
|
||||||
// size of 1 6126 byte block
|
// size of 1 6126 byte block
|
||||||
if ($remainingLength < 6126) {
|
if ($remainingLength < $this->unencryptedBlockSize) {
|
||||||
|
|
||||||
// Set writeCache to contents of $data
|
// Set writeCache to contents of $data
|
||||||
// The writeCache will be carried over to the
|
// The writeCache will be carried over to the
|
||||||
|
@ -293,14 +296,14 @@ class Encryption implements IEncryptionModule {
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Read the chunk from the start of $data
|
// Read the chunk from the start of $data
|
||||||
$chunk = substr($data, 0, 6126);
|
$chunk = substr($data, 0, $this->unencryptedBlockSize);
|
||||||
|
|
||||||
$encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey);
|
$encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey);
|
||||||
|
|
||||||
// Remove the chunk we just processed from
|
// Remove the chunk we just processed from
|
||||||
// $data, leaving only unprocessed data in $data
|
// $data, leaving only unprocessed data in $data
|
||||||
// var, for handling on the next round
|
// var, for handling on the next round
|
||||||
$data = substr($data, 6126);
|
$data = substr($data, $this->unencryptedBlockSize);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,7 +413,7 @@ class Encryption implements IEncryptionModule {
|
||||||
* @return integer
|
* @return integer
|
||||||
*/
|
*/
|
||||||
public function getUnencryptedBlockSize() {
|
public function getUnencryptedBlockSize() {
|
||||||
return 6126;
|
return $this->unencryptedBlockSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -29,7 +29,8 @@ $tmpl = new OCP\Template('encryption', 'settings-admin');
|
||||||
$crypt = new \OCA\Encryption\Crypto\Crypt(
|
$crypt = new \OCA\Encryption\Crypto\Crypt(
|
||||||
\OC::$server->getLogger(),
|
\OC::$server->getLogger(),
|
||||||
\OC::$server->getUserSession(),
|
\OC::$server->getUserSession(),
|
||||||
\OC::$server->getConfig());
|
\OC::$server->getConfig(),
|
||||||
|
\OC::$server->getL10N('encryption'));
|
||||||
|
|
||||||
$util = new \OCA\Encryption\Util(
|
$util = new \OCA\Encryption\Util(
|
||||||
new \OC\Files\View(),
|
new \OC\Files\View(),
|
||||||
|
|
|
@ -28,7 +28,8 @@ $template = new OCP\Template('encryption', 'settings-personal');
|
||||||
$crypt = new \OCA\Encryption\Crypto\Crypt(
|
$crypt = new \OCA\Encryption\Crypto\Crypt(
|
||||||
\OC::$server->getLogger(),
|
\OC::$server->getLogger(),
|
||||||
$userSession,
|
$userSession,
|
||||||
\OC::$server->getConfig());
|
\OC::$server->getConfig(),
|
||||||
|
\OC::$server->getL10N('encryption'));
|
||||||
|
|
||||||
$util = new \OCA\Encryption\Util(
|
$util = new \OCA\Encryption\Util(
|
||||||
new \OC\Files\View(),
|
new \OC\Files\View(),
|
||||||
|
|
|
@ -39,6 +39,10 @@ class cryptTest extends TestCase {
|
||||||
/** @var \PHPUnit_Framework_MockObject_MockObject */
|
/** @var \PHPUnit_Framework_MockObject_MockObject */
|
||||||
private $config;
|
private $config;
|
||||||
|
|
||||||
|
|
||||||
|
/** @var \PHPUnit_Framework_MockObject_MockObject */
|
||||||
|
private $l;
|
||||||
|
|
||||||
/** @var Crypt */
|
/** @var Crypt */
|
||||||
private $crypt;
|
private $crypt;
|
||||||
|
|
||||||
|
@ -57,8 +61,9 @@ class cryptTest extends TestCase {
|
||||||
$this->config = $this->getMockBuilder('OCP\IConfig')
|
$this->config = $this->getMockBuilder('OCP\IConfig')
|
||||||
->disableOriginalConstructor()
|
->disableOriginalConstructor()
|
||||||
->getMock();
|
->getMock();
|
||||||
|
$this->l = $this->getMock('OCP\IL10N');
|
||||||
|
|
||||||
$this->crypt = new Crypt($this->logger, $this->userSession, $this->config);
|
$this->crypt = new Crypt($this->logger, $this->userSession, $this->config, $this->l);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -89,7 +89,8 @@ class Controller {
|
||||||
$crypt = new \OCA\Encryption\Crypto\Crypt(
|
$crypt = new \OCA\Encryption\Crypto\Crypt(
|
||||||
\OC::$server->getLogger(),
|
\OC::$server->getLogger(),
|
||||||
\OC::$server->getUserSession(),
|
\OC::$server->getUserSession(),
|
||||||
\OC::$server->getConfig());
|
\OC::$server->getConfig(),
|
||||||
|
\OC::$server->getL10N('encryption'));
|
||||||
$keyStorage = \OC::$server->getEncryptionKeyStorage();
|
$keyStorage = \OC::$server->getEncryptionKeyStorage();
|
||||||
$util = new \OCA\Encryption\Util(
|
$util = new \OCA\Encryption\Util(
|
||||||
new \OC\Files\View(),
|
new \OC\Files\View(),
|
||||||
|
|
Loading…
Reference in New Issue