Use path instead of app id

This change requires the usage of a path instead of the App ID when signing code. This has the advantage that developers can also sign code under a different location to make it easier. (e.g. remove `.git`, …)

Also it adds an example command usage as well as a link to the documentation
remotes/origin/comments-markallread-dav
Lukas Reschke 9 years ago
parent 647d8ea5de
commit ea367b598a
  1. 25
      core/command/integrity/signapp.php
  2. 3
      core/register_command.php
  3. 11
      lib/private/integritycheck/checker.php
  4. 32
      tests/lib/command/integrity/SignAppTest.php
  5. 9
      tests/lib/integritycheck/checkertest.php

@ -23,6 +23,7 @@ namespace OC\Core\Command\Integrity;
use OC\IntegrityCheck\Checker;
use OC\IntegrityCheck\Helpers\FileAccessHelper;
use OCP\IURLGenerator;
use phpseclib\Crypt\RSA;
use phpseclib\File\X509;
use Symfony\Component\Console\Command\Command;
@ -40,23 +41,28 @@ class SignApp extends Command {
private $checker;
/** @var FileAccessHelper */
private $fileAccessHelper;
/** @var IURLGenerator */
private $urlGenerator;
/**
* @param Checker $checker
* @param FileAccessHelper $fileAccessHelper
* @param IURLGenerator $urlGenerator
*/
public function __construct(Checker $checker,
FileAccessHelper $fileAccessHelper) {
FileAccessHelper $fileAccessHelper,
IURLGenerator $urlGenerator) {
parent::__construct(null);
$this->checker = $checker;
$this->fileAccessHelper = $fileAccessHelper;
$this->urlGenerator = $urlGenerator;
}
protected function configure() {
$this
->setName('integrity:sign-app')
->setDescription('Sign app using a private key.')
->addOption('appId', null, InputOption::VALUE_REQUIRED, 'Application to sign')
->setDescription('Signs an app using a private key.')
->addOption('path', null, InputOption::VALUE_REQUIRED, 'Application to sign')
->addOption('privateKey', null, InputOption::VALUE_REQUIRED, 'Path to private key to use for signing')
->addOption('certificate', null, InputOption::VALUE_REQUIRED, 'Path to certificate to use for signing');
}
@ -65,11 +71,14 @@ class SignApp extends Command {
* {@inheritdoc }
*/
protected function execute(InputInterface $input, OutputInterface $output) {
$appId = $input->getOption('appId');
$path = $input->getOption('path');
$privateKeyPath = $input->getOption('privateKey');
$keyBundlePath = $input->getOption('certificate');
if(is_null($appId) || is_null($privateKeyPath) || is_null($keyBundlePath)) {
$output->writeln('--appId, --privateKey and --certificate are required.');
if(is_null($path) || is_null($privateKeyPath) || is_null($keyBundlePath)) {
$documentationUrl = $this->urlGenerator->linkToDocs('developer-code-integrity');
$output->writeln('This command requires the --path, --privateKey and --certificate.');
$output->writeln('Example: ./occ integrity:sign-app --path="/Users/lukasreschke/Programming/myapp/" --privateKey="/Users/lukasreschke/private/myapp.key" --certificate="/Users/lukasreschke/public/mycert.crt"');
$output->writeln('For more information please consult the documentation: '. $documentationUrl);
return null;
}
@ -91,8 +100,8 @@ class SignApp extends Command {
$x509 = new X509();
$x509->loadX509($keyBundle);
$x509->setPrivateKey($rsa);
$this->checker->writeAppSignature($appId, $x509, $rsa);
$this->checker->writeAppSignature($path, $x509, $rsa);
$output->writeln('Successfully signed "'.$appId.'"');
$output->writeln('Successfully signed "'.$path.'"');
}
}

@ -36,7 +36,8 @@ $application->add(new OC\Core\Command\App\CheckCode($infoParser));
$application->add(new OC\Core\Command\L10n\CreateJs());
$application->add(new \OC\Core\Command\Integrity\SignApp(
\OC::$server->getIntegrityCodeChecker(),
new \OC\IntegrityCheck\Helpers\FileAccessHelper()
new \OC\IntegrityCheck\Helpers\FileAccessHelper(),
\OC::$server->getURLGenerator()
));
$application->add(new \OC\Core\Command\Integrity\SignCore(
\OC::$server->getIntegrityCodeChecker(),

@ -114,6 +114,7 @@ class Checker {
*
* @param string $folderToIterate
* @return \RecursiveIteratorIterator
* @throws \Exception
*/
private function getFolderIterator($folderToIterate) {
$dirItr = new \RecursiveDirectoryIterator(
@ -189,17 +190,19 @@ class Checker {
}
/**
* Write the signature of the specified app
* Write the signature of the app in the specified folder
*
* @param string $appId
* @param string $path
* @param X509 $certificate
* @param RSA $privateKey
* @throws \Exception
*/
public function writeAppSignature($appId,
public function writeAppSignature($path,
X509 $certificate,
RSA $privateKey) {
$path = $this->appLocator->getAppPath($appId);
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);

@ -23,6 +23,7 @@ namespace Test\Command\Integrity;
use OC\Core\Command\Integrity\SignApp;
use OC\IntegrityCheck\Checker;
use OC\IntegrityCheck\Helpers\FileAccessHelper;
use OCP\IURLGenerator;
use Test\TestCase;
class SignAppTest extends TestCase {
@ -32,6 +33,8 @@ class SignAppTest extends TestCase {
private $signApp;
/** @var FileAccessHelper */
private $fileAccessHelper;
/** @var IURLGenerator */
private $urlGenerator;
public function setUp() {
parent::setUp();
@ -39,20 +42,23 @@ class SignAppTest extends TestCase {
->disableOriginalConstructor()->getMock();
$this->fileAccessHelper = $this->getMockBuilder('\OC\IntegrityCheck\Helpers\FileAccessHelper')
->disableOriginalConstructor()->getMock();
$this->urlGenerator = $this->getMockBuilder('\OCP\IURLGenerator')
->disableOriginalConstructor()->getMock();
$this->signApp = new SignApp(
$this->checker,
$this->fileAccessHelper
$this->fileAccessHelper,
$this->urlGenerator
);
}
public function testExecuteWithMissingAppId() {
public function testExecuteWithMissingPath() {
$inputInterface = $this->getMock('\Symfony\Component\Console\Input\InputInterface');
$outputInterface = $this->getMock('\Symfony\Component\Console\Output\OutputInterface');
$inputInterface
->expects($this->at(0))
->method('getOption')
->with('appId')
->with('path')
->will($this->returnValue(null));
$inputInterface
->expects($this->at(1))
@ -68,7 +74,7 @@ class SignAppTest extends TestCase {
$outputInterface
->expects($this->at(0))
->method('writeln')
->with('--appId, --privateKey and --certificate are required.');
->with('This command requires the --path, --privateKey and --certificate.');
$this->invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]);
}
@ -80,7 +86,7 @@ class SignAppTest extends TestCase {
$inputInterface
->expects($this->at(0))
->method('getOption')
->with('appId')
->with('path')
->will($this->returnValue('AppId'));
$inputInterface
->expects($this->at(1))
@ -94,9 +100,9 @@ class SignAppTest extends TestCase {
->will($this->returnValue('Certificate'));
$outputInterface
->expects($this->at(0))
->method('writeln')
->with('--appId, --privateKey and --certificate are required.');
->expects($this->at(0))
->method('writeln')
->with('This command requires the --path, --privateKey and --certificate.');
$this->invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]);
}
@ -108,7 +114,7 @@ class SignAppTest extends TestCase {
$inputInterface
->expects($this->at(0))
->method('getOption')
->with('appId')
->with('path')
->will($this->returnValue('AppId'));
$inputInterface
->expects($this->at(1))
@ -124,7 +130,7 @@ class SignAppTest extends TestCase {
$outputInterface
->expects($this->at(0))
->method('writeln')
->with('--appId, --privateKey and --certificate are required.');
->with('This command requires the --path, --privateKey and --certificate.');
$this->invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]);
}
@ -136,7 +142,7 @@ class SignAppTest extends TestCase {
$inputInterface
->expects($this->at(0))
->method('getOption')
->with('appId')
->with('path')
->will($this->returnValue('AppId'));
$inputInterface
->expects($this->at(1))
@ -170,7 +176,7 @@ class SignAppTest extends TestCase {
$inputInterface
->expects($this->at(0))
->method('getOption')
->with('appId')
->with('path')
->will($this->returnValue('AppId'));
$inputInterface
->expects($this->at(1))
@ -209,7 +215,7 @@ class SignAppTest extends TestCase {
$inputInterface
->expects($this->at(0))
->method('getOption')
->with('appId')
->with('path')
->will($this->returnValue('AppId'));
$inputInterface
->expects($this->at(1))

@ -77,7 +77,7 @@ class CheckerTest extends TestCase {
/**
* @expectedException \Exception
* @expectedExceptionMessage Directory name must not be empty.
* @expectedExceptionMessage Directory does not exist.
*/
public function testWriteAppSignatureOfNotExistingApp() {
$keyBundle = file_get_contents(__DIR__ .'/../../data/integritycheck/SomeApp.crt');
@ -98,11 +98,6 @@ class CheckerTest extends TestCase {
"signature": "Y5yvXvcGHVPuRRatKVDUONWq1FpLXugZd6Km\/+aEHsQj7coVl9FeMj9OsWamBf7yRIw3dtNLguTLlAA9QAv\/b0uHN3JnbNZN+dwFOve4NMtqXfSDlWftqKN00VS+RJXpG1S2IIx9Poyp2NoghL\/5AuTv4GHiNb7zU\/DT\/kt71pUGPgPR6IIFaE+zHOD96vjYkrH+GfWZzKR0FCdLib9yyNvk+EGrcjKM6qjs2GKfS\/XFjj\/\/neDnh\/0kcPuKE3ZbofnI4TIDTv0CGqvOp7PtqVNc3Vy\/UKa7uF1PT0MAUKMww6EiMUSFZdUVP4WWF0Y72W53Qdtf1hrAZa2kfKyoK5kd7sQmCSKUPSU8978AUVZlBtTRlyT803IKwMV0iHMkw+xYB1sN2FlHup\/DESADqxhdgYuK35bCPvgkb4SBe4B8Voz\/izTvcP7VT5UvkYdAO+05\/jzdaHEmzmsD92CFfvX0q8O\/Y\/29ubftUJsqcHeMDKgcR4eZOE8+\/QVc\/89QO6WnKNuNuV+5bybO6g6PAdC9ZPsCvnihS61O2mwRXHLR3jv2UleFWm+lZEquPKtkhi6SLtDiijA4GV6dmS+dzujSLb7hGeD5o1plZcZ94uhWljl+QIp82+zU\/lYB1Zfr4Mb4e+V7r2gv7Fbv7y6YtjE2GIQwRhC5jq56bD0ZB+I=",
"certificate": "-----BEGIN CERTIFICATE-----\r\nMIIEwTCCAqmgAwIBAgIUWv0iujufs5lUr0svCf\/qTQvoyKAwDQYJKoZIhvcNAQEF\r\nBQAwIzEhMB8GA1UECgwYb3duQ2xvdWQgQ29kZSBTaWduaW5nIENBMB4XDTE1MTEw\r\nMzIyNDk1M1oXDTE2MTEwMzIyNDk1M1owEjEQMA4GA1UEAwwHU29tZUFwcDCCAiIw\r\nDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK8q0x62agGSRBqeWsaeEwFfepMk\r\nF8cAobMMi50qHCv9IrOn\/ZH9l52xBrbIkErVmRjmly0d4JhD8Ymhidsh9ONKYl\/j\r\n+ishsZDM8eNNdp3Ew+fEYVvY1W7mR1qU24NWj0bzVsClI7hvPVIuw7AjfBDq1C5+\r\nA+ZSLSXYvOK2cEWjdxQfuNZwEZSjmA63DUllBIrm35IaTvfuyhU6BW9yHZxmb8+M\r\nw0xDv30D5UkE\/2N7Pa\/HQJLxCR+3zKibRK3nUyRDLSXxMkU9PnFNaPNX59VPgyj4\r\nGB1CFSToldJVPF4pzh7p36uGXZVxs8m3LFD4Ol8mhi7jkxDZjqFN46gzR0r23Py6\r\ndol9vfawGIoUwp9LvL0S7MvdRY0oazLXwClLP4OQ17zpSMAiCj7fgNT661JamPGj\r\nt5O7Zn2wA7I4ddDS\/HDTWCu98Zwc9fHIpsJPgCZ9awoqxi4Mnf7Pk9g5nnXhszGC\r\ncxxIASQKM+GhdzoRxKknax2RzUCwCzcPRtCj8AQT\/x\/mqN3PfRmlnFBNACUw9bpZ\r\nSOoNq2pCF9igftDWpSIXQ38pVpKLWowjjg3DVRmVKBgivHnUnVLyzYBahHPj0vaz\r\ntFtUFRaqXDnt+4qyUGyrT5h5pjZaTcHIcSB4PiarYwdVvgslgwnQzOUcGAzRWBD4\r\n6jV2brP5vFY3g6iPAgMBAAEwDQYJKoZIhvcNAQEFBQADggIBACTY3CCHC+Z28gCf\r\nFWGKQ3wAKs+k4+0yoti0qm2EKX7rSGQ0PHSas6uW79WstC4Rj+DYkDtIhGMSg8FS\r\nHVGZHGBCc0HwdX+BOAt3zi4p7Sf3oQef70\/4imPoKxbAVCpd\/cveVcFyDC19j1yB\r\nBapwu87oh+muoeaZxOlqQI4UxjBlR\/uRSMhOn2UGauIr3dWJgAF4pGt7TtIzt+1v\r\n0uA6FtN1Y4R5O8AaJPh1bIG0CVvFBE58esGzjEYLhOydgKFnEP94kVPgJD5ds9C3\r\npPhEpo1dRpiXaF7WGIV1X6DI\/ipWvfrF7CEy6I\/kP1InY\/vMDjQjeDnJ\/VrXIWXO\r\nyZvHXVaN\/m+1RlETsH7YO\/QmxRue9ZHN3gvvWtmpCeA95sfpepOk7UcHxHZYyQbF\r\n49\/au8j+5tsr4A83xzsT1JbcKRxkAaQ7WDJpOnE5O1+H0fB+BaLakTg6XX9d4Fo7\r\n7Gin7hVWX7pL+JIyxMzME3LhfI61+CRcqZQIrpyaafUziPQbWIPfEs7h8tCOWyvW\r\nUO8ZLervYCB3j44ivkrxPlcBklDCqqKKBzDP9dYOtS\/P4RB1NkHA9+NTvmBpTonS\r\nSFXdg9fFMD7VfjDE3Vnk+8DWkVH5wBYowTAD7w9Wuzr7DumiAULexnP\/Y7xwxLv7\r\n4B+pXTAcRK0zECDEaX3npS8xWzrB\r\n-----END CERTIFICATE-----"
}';
$this->appLocator
->expects($this->once())
->method('getAppPath')
->with('SomeExistingApp')
->will($this->returnValue(\OC::$SERVERROOT . '/tests/data/integritycheck/app/'));
$this->fileAccessHelper
->expects($this->once())
->method('file_put_contents')
@ -117,7 +112,7 @@ class CheckerTest extends TestCase {
$rsa->loadKey($rsaPrivateKey);
$x509 = new X509();
$x509->loadX509($keyBundle);
$this->checker->writeAppSignature('SomeExistingApp', $x509, $rsa);
$this->checker->writeAppSignature(\OC::$SERVERROOT . '/tests/data/integritycheck/app/', $x509, $rsa);
}
public function testVerifyAppSignatureWithoutSignatureData() {

Loading…
Cancel
Save