Merge pull request #2724 from nextcloud/fix-23591

[downstream] Report failures for SignApp and SignCore
pull/2808/head
Lukas Reschke 8 years ago committed by GitHub
commit 091bf07385

@ -101,8 +101,13 @@ class SignApp extends Command {
$x509 = new X509();
$x509->loadX509($keyBundle);
$x509->setPrivateKey($rsa);
$this->checker->writeAppSignature($path, $x509, $rsa);
$output->writeln('Successfully signed "'.$path.'"');
try {
$this->checker->writeAppSignature($path, $x509, $rsa);
$output->writeln('Successfully signed "'.$path.'"');
} catch (\Exception $e){
$output->writeln('Error: ' . $e->getMessage());
return 1;
}
return 0;
}
}

@ -23,12 +23,10 @@
namespace OC\Core\Command\Integrity;
use OC\IntegrityCheck\Checker;
use OC\IntegrityCheck\Helpers\EnvironmentHelper;
use OC\IntegrityCheck\Helpers\FileAccessHelper;
use phpseclib\Crypt\RSA;
use phpseclib\File\X509;
use Symfony\Component\Console\Command\Command;
use OCP\IConfig;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@ -94,8 +92,14 @@ class SignCore extends Command {
$x509 = new X509();
$x509->loadX509($keyBundle);
$x509->setPrivateKey($rsa);
$this->checker->writeCoreSignature($x509, $rsa, $path);
$output->writeln('Successfully signed "core"');
try {
$this->checker->writeCoreSignature($x509, $rsa, $path);
$output->writeln('Successfully signed "core"');
} catch (\Exception $e){
$output->writeln('Error: ' . $e->getMessage());
return 1;
}
return 0;
}
}

@ -267,16 +267,23 @@ class Checker {
public function writeAppSignature($path,
X509 $certificate,
RSA $privateKey) {
if(!is_dir($path)) {
throw new \Exception('Directory does not exist.');
}
$iterator = $this->getFolderIterator($path);
$hashes = $this->generateHashes($iterator, $path);
$signature = $this->createSignatureData($hashes, $certificate, $privateKey);
$this->fileAccessHelper->file_put_contents(
$path . '/appinfo/signature.json',
$appInfoDir = $path . '/appinfo';
try {
$this->fileAccessHelper->assertDirectoryExists($appInfoDir);
$iterator = $this->getFolderIterator($path);
$hashes = $this->generateHashes($iterator, $path);
$signature = $this->createSignatureData($hashes, $certificate, $privateKey);
$this->fileAccessHelper->file_put_contents(
$appInfoDir . '/signature.json',
json_encode($signature, JSON_PRETTY_PRINT)
);
);
} catch (\Exception $e){
if (!$this->fileAccessHelper->is_writable($appInfoDir)) {
throw new \Exception($appInfoDir . ' is not writable');
}
throw $e;
}
}
/**
@ -285,17 +292,28 @@ class Checker {
* @param X509 $certificate
* @param RSA $rsa
* @param string $path
* @throws \Exception
*/
public function writeCoreSignature(X509 $certificate,
RSA $rsa,
$path) {
$iterator = $this->getFolderIterator($path, $path);
$hashes = $this->generateHashes($iterator, $path);
$signatureData = $this->createSignatureData($hashes, $certificate, $rsa);
$this->fileAccessHelper->file_put_contents(
$path . '/core/signature.json',
$coreDir = $path . '/core';
try {
$this->fileAccessHelper->assertDirectoryExists($coreDir);
$iterator = $this->getFolderIterator($path, $path);
$hashes = $this->generateHashes($iterator, $path);
$signatureData = $this->createSignatureData($hashes, $certificate, $rsa);
$this->fileAccessHelper->file_put_contents(
$coreDir . '/signature.json',
json_encode($signatureData, JSON_PRETTY_PRINT)
);
);
} catch (\Exception $e){
if (!$this->fileAccessHelper->is_writable($coreDir)) {
throw new \Exception($coreDir . ' is not writable');
}
throw $e;
}
}
/**

@ -53,10 +53,33 @@ class FileAccessHelper {
* Wrapper around file_put_contents($filename, $data)
*
* @param string $filename
* @param $data
* @return int|false
* @param string $data
* @return int
* @throws \Exception
*/
public function file_put_contents($filename, $data) {
return file_put_contents($filename, $data);
$bytesWritten = @file_put_contents($filename, $data);
if ($bytesWritten === false || $bytesWritten !== strlen($data)){
throw new \Exception('Failed to write into ' . $filename);
}
return $bytesWritten;
}
/**
* @param string $path
* @return bool
*/
public function is_writable($path) {
return is_writable($path);
}
/**
* @param string $path
* @throws \Exception
*/
public function assertDirectoryExists($path) {
if (!is_dir($path)) {
throw new \Exception('Directory ' . $path . ' does not exist.');
}
}
}

@ -29,13 +29,13 @@ use Symfony\Component\Console\Output\OutputInterface;
use Test\TestCase;
class SignAppTest extends TestCase {
/** @var Checker */
/** @var Checker|\PHPUnit_Framework_MockObject_MockObject */
private $checker;
/** @var SignApp */
private $signApp;
/** @var FileAccessHelper */
/** @var FileAccessHelper|\PHPUnit_Framework_MockObject_MockObject */
private $fileAccessHelper;
/** @var IURLGenerator */
/** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */
private $urlGenerator;
public function setUp() {
@ -75,7 +75,7 @@ class SignAppTest extends TestCase {
->method('writeln')
->with('This command requires the --path, --privateKey and --certificate.');
$this->invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]);
$this->assertNull(self::invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]));
}
public function testExecuteWithMissingPrivateKey() {
@ -103,7 +103,7 @@ class SignAppTest extends TestCase {
->method('writeln')
->with('This command requires the --path, --privateKey and --certificate.');
$this->invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]);
$this->assertNull(self::invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]));
}
public function testExecuteWithMissingCertificate() {
@ -131,7 +131,7 @@ class SignAppTest extends TestCase {
->method('writeln')
->with('This command requires the --path, --privateKey and --certificate.');
$this->invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]);
$this->assertNull(self::invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]));
}
public function testExecuteWithNotExistingPrivateKey() {
@ -165,7 +165,7 @@ class SignAppTest extends TestCase {
->method('writeln')
->with('Private key "privateKey" does not exists.');
$this->invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]);
$this->assertNull(self::invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]));
}
public function testExecuteWithNotExistingCertificate() {
@ -204,7 +204,51 @@ class SignAppTest extends TestCase {
->method('writeln')
->with('Certificate "certificate" does not exists.');
$this->invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]);
$this->assertNull(self::invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]));
}
public function testExecuteWithException() {
$inputInterface = $this->createMock(InputInterface::class);
$outputInterface = $this->createMock(OutputInterface::class);
$inputInterface
->expects($this->at(0))
->method('getOption')
->with('path')
->will($this->returnValue('AppId'));
$inputInterface
->expects($this->at(1))
->method('getOption')
->with('privateKey')
->will($this->returnValue('privateKey'));
$inputInterface
->expects($this->at(2))
->method('getOption')
->with('certificate')
->will($this->returnValue('certificate'));
$this->fileAccessHelper
->expects($this->at(0))
->method('file_get_contents')
->with('privateKey')
->will($this->returnValue(\OC::$SERVERROOT . '/tests/data/integritycheck/core.key'));
$this->fileAccessHelper
->expects($this->at(1))
->method('file_get_contents')
->with('certificate')
->will($this->returnValue(\OC::$SERVERROOT . '/tests/data/integritycheck/core.crt'));
$this->checker
->expects($this->once())
->method('writeAppSignature')
->willThrowException(new \Exception('My error message'));
$outputInterface
->expects($this->at(0))
->method('writeln')
->with('Error: My error message');
$this->assertSame(1, self::invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]));
}
public function testExecute() {
@ -247,6 +291,6 @@ class SignAppTest extends TestCase {
->method('writeln')
->with('Successfully signed "AppId"');
$this->invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]);
$this->assertSame(0, self::invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]));
}
}

@ -28,12 +28,12 @@ use Symfony\Component\Console\Output\OutputInterface;
use Test\TestCase;
class SignCoreTest extends TestCase {
/** @var Checker */
/** @var Checker|\PHPUnit_Framework_MockObject_MockObject */
private $checker;
/** @var FileAccessHelper|\PHPUnit_Framework_MockObject_MockObject */
private $fileAccessHelper;
/** @var SignCore */
private $signCore;
/** @var FileAccessHelper */
private $fileAccessHelper;
public function setUp() {
parent::setUp();
@ -65,7 +65,7 @@ class SignCoreTest extends TestCase {
->method('writeln')
->with('--privateKey, --certificate and --path are required.');
$this->invokePrivate($this->signCore, 'execute', [$inputInterface, $outputInterface]);
$this->assertNull(self::invokePrivate($this->signCore, 'execute', [$inputInterface, $outputInterface]));
}
public function testExecuteWithMissingCertificate() {
@ -88,7 +88,7 @@ class SignCoreTest extends TestCase {
->method('writeln')
->with('--privateKey, --certificate and --path are required.');
$this->invokePrivate($this->signCore, 'execute', [$inputInterface, $outputInterface]);
$this->assertNull(self::invokePrivate($this->signCore, 'execute', [$inputInterface, $outputInterface]));
}
public function testExecuteWithNotExistingPrivateKey() {
@ -122,7 +122,7 @@ class SignCoreTest extends TestCase {
->method('writeln')
->with('Private key "privateKey" does not exists.');
$this->invokePrivate($this->signCore, 'execute', [$inputInterface, $outputInterface]);
$this->assertNull(self::invokePrivate($this->signCore, 'execute', [$inputInterface, $outputInterface]));
}
public function testExecuteWithNotExistingCertificate() {
@ -161,7 +161,51 @@ class SignCoreTest extends TestCase {
->method('writeln')
->with('Certificate "certificate" does not exists.');
$this->invokePrivate($this->signCore, 'execute', [$inputInterface, $outputInterface]);
$this->assertNull(self::invokePrivate($this->signCore, 'execute', [$inputInterface, $outputInterface]));
}
public function testExecuteWithException() {
$inputInterface = $this->createMock(InputInterface::class);
$outputInterface = $this->createMock(OutputInterface::class);
$inputInterface
->expects($this->at(0))
->method('getOption')
->with('privateKey')
->will($this->returnValue('privateKey'));
$inputInterface
->expects($this->at(1))
->method('getOption')
->with('certificate')
->will($this->returnValue('certificate'));
$inputInterface
->expects($this->at(2))
->method('getOption')
->with('path')
->will($this->returnValue('certificate'));
$this->fileAccessHelper
->expects($this->at(0))
->method('file_get_contents')
->with('privateKey')
->will($this->returnValue(\OC::$SERVERROOT . '/tests/data/integritycheck/core.key'));
$this->fileAccessHelper
->expects($this->at(1))
->method('file_get_contents')
->with('certificate')
->will($this->returnValue(\OC::$SERVERROOT . '/tests/data/integritycheck/core.crt'));
$this->checker
->expects($this->once())
->method('writeCoreSignature')
->willThrowException(new \Exception('My exception message'));
$outputInterface
->expects($this->at(0))
->method('writeln')
->with('Error: My exception message');
$this->assertEquals(1, self::invokePrivate($this->signCore, 'execute', [$inputInterface, $outputInterface]));
}
public function testExecute() {
@ -204,6 +248,6 @@ class SignCoreTest extends TestCase {
->method('writeln')
->with('Successfully signed "core"');
$this->invokePrivate($this->signCore, 'execute', [$inputInterface, $outputInterface]);
$this->assertEquals(0, self::invokePrivate($this->signCore, 'execute', [$inputInterface, $outputInterface]));
}
}

@ -34,19 +34,19 @@ use OCP\ICacheFactory;
use OCP\App\IAppManager;
class CheckerTest extends TestCase {
/** @var EnvironmentHelper */
/** @var EnvironmentHelper|\PHPUnit_Framework_MockObject_MockObject */
private $environmentHelper;
/** @var AppLocator */
/** @var AppLocator|\PHPUnit_Framework_MockObject_MockObject */
private $appLocator;
/** @var Checker */
private $checker;
/** @var FileAccessHelper */
/** @var FileAccessHelper|\PHPUnit_Framework_MockObject_MockObject */
private $fileAccessHelper;
/** @var IConfig */
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
private $config;
/** @var ICacheFactory */
/** @var ICacheFactory|\PHPUnit_Framework_MockObject_MockObject */
private $cacheFactory;
/** @var IAppManager */
/** @var IAppManager|\PHPUnit_Framework_MockObject_MockObject */
private $appManager;
public function setUp() {
@ -77,9 +77,20 @@ class CheckerTest extends TestCase {
/**
* @expectedException \Exception
* @expectedExceptionMessage Directory does not exist.
* @expectedExceptionMessage Exception message
*/
public function testWriteAppSignatureOfNotExistingApp() {
$this->fileAccessHelper
->expects($this->at(0))
->method('assertDirectoryExists')
->with('NotExistingApp/appinfo')
->willThrowException(new \Exception('Exception message'));
$this->fileAccessHelper
->expects($this->at(1))
->method('is_writable')
->with('NotExistingApp/appinfo')
->willReturn(true);
$keyBundle = file_get_contents(__DIR__ .'/../../data/integritycheck/SomeApp.crt');
$rsaPrivateKey = file_get_contents(__DIR__ .'/../../data/integritycheck/SomeApp.key');
$rsa = new RSA();
@ -89,6 +100,25 @@ class CheckerTest extends TestCase {
$this->checker->writeAppSignature('NotExistingApp', $x509, $rsa);
}
/**
* @expectedException \Exception
* @expectedExceptionMessageRegExp /[a-zA-Z\/_-]+ is not writable/
*/
public function testWriteAppSignatureWrongPermissions() {
$this->fileAccessHelper
->expects($this->once())
->method('file_put_contents')
->will($this->throwException(new \Exception('Exception message')));
$keyBundle = file_get_contents(__DIR__ .'/../../data/integritycheck/SomeApp.crt');
$rsaPrivateKey = file_get_contents(__DIR__ .'/../../data/integritycheck/SomeApp.key');
$rsa = new RSA();
$rsa->loadKey($rsaPrivateKey);
$x509 = new X509();
$x509->loadX509($keyBundle);
$this->checker->writeAppSignature(\OC::$SERVERROOT . '/tests/data/integritycheck/app/', $x509, $rsa);
}
public function testWriteAppSignature() {
$expectedSignatureFileData = '{
"hashes": {
@ -443,6 +473,54 @@ class CheckerTest extends TestCase {
$this->assertSame([], $this->checker->verifyAppSignature('SomeApp'));
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Exception message
*/
public function testWriteCoreSignatureWithException() {
$this->fileAccessHelper
->expects($this->at(0))
->method('assertDirectoryExists')
->will($this->throwException(new \Exception('Exception message')));
$this->fileAccessHelper
->expects($this->at(1))
->method('is_writable')
->with(__DIR__ . '/core')
->willReturn(true);
$keyBundle = file_get_contents(__DIR__ .'/../../data/integritycheck/SomeApp.crt');
$rsaPrivateKey = file_get_contents(__DIR__ .'/../../data/integritycheck/SomeApp.key');
$rsa = new RSA();
$rsa->loadKey($rsaPrivateKey);
$x509 = new X509();
$x509->loadX509($keyBundle);
$this->checker->writeCoreSignature($x509, $rsa, __DIR__);
}
/**
* @expectedException \Exception
* @expectedExceptionMessageRegExp /[a-zA-Z\/_-]+ is not writable/
*/
public function testWriteCoreSignatureWrongPermissions() {
$this->fileAccessHelper
->expects($this->at(0))
->method('assertDirectoryExists')
->will($this->throwException(new \Exception('Exception message')));
$this->fileAccessHelper
->expects($this->at(1))
->method('is_writable')
->with(__DIR__ . '/core')
->willReturn(false);
$keyBundle = file_get_contents(__DIR__ .'/../../data/integritycheck/SomeApp.crt');
$rsaPrivateKey = file_get_contents(__DIR__ .'/../../data/integritycheck/SomeApp.key');
$rsa = new RSA();
$rsa->loadKey($rsaPrivateKey);
$x509 = new X509();
$x509->loadX509($keyBundle);
$this->checker->writeCoreSignature($x509, $rsa, __DIR__);
}
public function testWriteCoreSignature() {
$expectedSignatureFileData = '{
"hashes": {
@ -948,7 +1026,7 @@ class CheckerTest extends TestCase {
->method('verifyCoreSignature');
$this->appLocator
->expects($this->at(0))
->Method('getAllApps')
->method('getAllApps')
->will($this->returnValue([
'files',
'calendar',
@ -1074,7 +1152,6 @@ class CheckerTest extends TestCase {
->with('integrity.check.disabled', false)
->will($this->returnValue(true));
$result = $this->invokePrivate($this->checker, 'isCodeCheckEnforced');
$this->assertSame(false, $result);
$this->assertFalse(self::invokePrivate($this->checker, 'isCodeCheckEnforced'));
}
}

@ -40,4 +40,29 @@ class FileAccessHelperTest extends TestCase {
$this->fileAccessHelper->file_put_contents($filePath, $data);
$this->assertSame($data, $this->fileAccessHelper->file_get_contents($filePath));
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Failed to write into /anabsolutelynotexistingfolder/on/the/system.txt
*/
public function testFile_put_contentsWithException() {
$this->fileAccessHelper->file_put_contents('/anabsolutelynotexistingfolder/on/the/system.txt', 'MyFiles');
}
public function testIs_writable() {
$this->assertFalse($this->fileAccessHelper->is_writable('/anabsolutelynotexistingfolder/on/the/system.txt'));
$this->assertTrue($this->fileAccessHelper->is_writable(\OC::$server->getTempManager()->getTemporaryFile('MyFile')));
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Directory /anabsolutelynotexistingfolder/on/the/system does not exist.
*/
public function testAssertDirectoryExistsWithException() {
$this->fileAccessHelper->assertDirectoryExists('/anabsolutelynotexistingfolder/on/the/system');
}
public function testAssertDirectoryExists() {
$this->fileAccessHelper->assertDirectoryExists(\OC::$server->getTempManager()->getTemporaryFolder('/testfolder/'));
}
}

Loading…
Cancel
Save