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
This commit is contained in:
parent
647d8ea5de
commit
ea367b598a
|
@ -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…
Reference in New Issue