From 084f46917a53e6997317283ac0d445a02740c974 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 13 Jul 2016 16:12:25 +0200 Subject: [stable9.1] Adding certificate revocation list and validate if the app certificate is revoked (#25468) * Adding certificate revocation list and validate if the app certificate is revoked * Check integrity of a signed app in any case on installation --- lib/private/Installer.php | 2 +- lib/private/IntegrityCheck/Checker.php | 24 +++++++++- resources/codesigning/intermediate.crl.pem | 21 +++++++++ tests/lib/IntegrityCheck/CheckerTest.php | 72 ++++++++++++++++++++++++++---- 4 files changed, 108 insertions(+), 11 deletions(-) create mode 100644 resources/codesigning/intermediate.crl.pem diff --git a/lib/private/Installer.php b/lib/private/Installer.php index e8872c6662f..6e54d9e102a 100644 --- a/lib/private/Installer.php +++ b/lib/private/Installer.php @@ -367,7 +367,7 @@ class Installer { $appBelongingToId = $info['id']; $previouslySigned = 'false'; } - if($data['appdata']['level'] === OC_App::officialApp || $previouslySigned === 'true') { + if (file_exists($extractDir . '/appinfo/signature.json') || $previouslySigned === 'true') { \OC::$server->getConfig()->setAppValue($appBelongingToId, 'signed', 'true'); $integrityResult = \OC::$server->getIntegrityCodeChecker()->verifyAppSignature( $appBelongingToId, diff --git a/lib/private/IntegrityCheck/Checker.php b/lib/private/IntegrityCheck/Checker.php index 57127f280c4..d4038f85302 100644 --- a/lib/private/IntegrityCheck/Checker.php +++ b/lib/private/IntegrityCheck/Checker.php @@ -327,10 +327,30 @@ class Checker { $x509 = new \phpseclib\File\X509(); $rootCertificatePublicKey = $this->fileAccessHelper->file_get_contents($this->environmentHelper->getServerRoot().'/resources/codesigning/root.crt'); $x509->loadCA($rootCertificatePublicKey); - $x509->loadX509($certificate); + $loadedCertificate = $x509->loadX509($certificate); if(!$x509->validateSignature()) { - throw new InvalidSignatureException('Certificate is not valid.'); + throw new InvalidSignatureException('App Certificate is not valid.'); } + + // Check if the certificate has been revoked + $crlFileContent = $this->fileAccessHelper->file_get_contents($this->environmentHelper->getServerRoot().'/resources/codesigning/intermediate.crl.pem'); + if ($crlFileContent && strlen($crlFileContent) > 0) { + $crl = new \phpseclib\File\X509(); + $crl->loadCA($rootCertificatePublicKey); + $crl->loadCRL($crlFileContent); + if(!$crl->validateSignature()) { + throw new InvalidSignatureException('Certificate Revocation List is not valid.'); + } + // Get the certificate's serial number. + $csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString(); + + // Check certificate revocation status. + $revoked = $crl->getRevoked($csn); + if ($revoked) { + throw new InvalidSignatureException('Certificate has been revoked.'); + } + } + // Verify if certificate has proper CN. "core" CN is always trusted. if($x509->getDN(X509::DN_OPENSSL)['CN'] !== $certificateCN && $x509->getDN(X509::DN_OPENSSL)['CN'] !== 'core') { throw new InvalidSignatureException( diff --git a/resources/codesigning/intermediate.crl.pem b/resources/codesigning/intermediate.crl.pem new file mode 100644 index 00000000000..f6e54cf0dbc --- /dev/null +++ b/resources/codesigning/intermediate.crl.pem @@ -0,0 +1,21 @@ +-----BEGIN X509 CRL----- +MIIDdDCCAVwCAQEwDQYJKoZIhvcNAQELBQAwbTELMAkGA1UEBhMCVVMxDzANBgNV +BAgMBkJvc3RvbjEWMBQGA1UECgwNb3duQ2xvdWQgSW5jLjE1MDMGA1UEAwwsb3du +Q2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBBdXRob3JpdHkXDTE2MDcx +MjEzMjYyMFoXDTI2MDcxMDEzMjYyMFowFTATAgIQCBcNMTYwNzEyMTMyNDE2WqCB +ozCBoDCBkAYDVR0jBIGIMIGFgBR9mg09Gd5DWmT1JlP0DaWkbgfDs6FppGcwZTEL +MAkGA1UEBhMCVVMxDzANBgNVBAgMBkJvc3RvbjEWMBQGA1UECgwNb3duQ2xvdWQg +SW5jLjEtMCsGA1UEAwwkb3duQ2xvdWQgQ29kZSBTaWduaW5nIFJvb3QgQXV0aG9y +aXR5ggIQADALBgNVHRQEBAICEAIwDQYJKoZIhvcNAQELBQADggIBAJ+HZMvR1doo +t/l7cjv4Nt6BOugrTv0WzLWz6hhPS305l5kwWyekLzl+4UJLfmX0uEGAMxi370GZ +Ljwgeu/sIlP0ZEDr6+Py2ddSP9ZKvfymOx3Ihzr+7aa99HZvG2wx4N6yvy2dYBkD +CiE7RRdmO43vZYFAAcRPOlJwaKepk0Zyg2Hr4WWNsqRPkDYkWk3Zf85rRhwwmO8a +2UNAe4BiCRQeNBbOgoNkA7vETJKO7NyVshBrBkosFfXFe4S7SIOJLtgHKjk5Z7Hn +X46eEsnndOLVZiBZ1p0I5tTY4iQbGNxy0ZHwCMDjLsPfD4CaosT/YX74anMmYVtX +YU3k5sweg/bHBJx2oKeVk0L0n2HH2gfpwL1zuJjzwuN/AB/VbogRLEzT2MKJ8clU +nYlNKao7FRyiS1L+1sBNYyBFyAhHfs4ddX5KzZIWD9A4nYW22Xw+1azaykCNdrrB +4dCGdi3QbyTXodx/J5r4iHmkF3JW7g0bNmw0GFHUwrbTWcfGsyjDfXKuae9sjMTV +Ud62ee+7If+89UvKq6El+d1SEOCyy7vK+3cHPilbWRc/735qfBeD8JWir3V9BTcS +lNE51CbvI5t90H+Nu1XRae9KSLMk/bpfF7NO/rXebB6GCvCmaSKDlkuhqqKBKa/q +jJX4kqB0cB2GJT8CHq7sQEGjyjLrXWQ3 +-----END X509 CRL----- diff --git a/tests/lib/IntegrityCheck/CheckerTest.php b/tests/lib/IntegrityCheck/CheckerTest.php index 6e6095668b0..75e923da7da 100644 --- a/tests/lib/IntegrityCheck/CheckerTest.php +++ b/tests/lib/IntegrityCheck/CheckerTest.php @@ -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() { @@ -848,7 +848,7 @@ class CheckerTest extends TestCase { $expected = [ 'EXCEPTION' => [ 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', - 'message' => 'Certificate is not valid.', + 'message' => 'App Certificate is not valid.', ] ]; $this->assertSame($expected, $this->checker->verifyCoreSignature()); @@ -923,7 +923,7 @@ class CheckerTest extends TestCase { ->method('verifyCoreSignature'); $this->appLocator ->expects($this->at(0)) - ->Method('getAllApps') + ->method('getAllApps') ->will($this->returnValue([ 'files', 'calendar', @@ -1052,4 +1052,60 @@ class CheckerTest extends TestCase { $result = $this->invokePrivate($this->checker, 'isCodeCheckEnforced'); $this->assertSame(false, $result); } + + public function testCertRevocation() { + $this->environmentHelper + ->expects($this->once()) + ->method('getChannel') + ->will($this->returnValue('stable')); + $this->config + ->expects($this->any()) + ->method('getSystemValue') + ->with('integrity.check.disabled', false) + ->will($this->returnValue(false)); + + $this->appLocator + ->expects($this->once()) + ->method('getAppPath') + ->with('SomeApp') + ->will($this->returnValue(\OC::$SERVERROOT . '/tests/data/integritycheck/app/')); + $signatureDataFile = '{ + "hashes": { + "AnotherFile.txt": "1570ca9420e37629de4328f48c51da29840ddeaa03ae733da4bf1d854b8364f594aac560601270f9e1797ed4cd57c1aea87bf44cf4245295c94f2e935a2f0112", + "subfolder\/file.txt": "410738545fb623c0a5c8a71f561e48ea69e3ada0981a455e920a5ae9bf17c6831ae654df324f9328ff8453de179276ae51931cca0fa71fe8ccde6c083ca0574b" + }, + "signature": "dYoohBaWIFR\/To1FXEbMQB5apUhVYlEauBGSPo12nq84wxWkBx2EM3KDRgkB5Sub2tr0CgmAc2EVjPhKIEzAam26cyUb48bJziz1V6wvW7z4GZAfaJpzLkyHdSfV5117VSf5w1rDcAeZDXfGUaaNEJPWytaF4ZIxVge7f3NGshHy4odFVPADy\/u6c43BWvaOtJ4m3aJQbP6sxCO9dxwcm5yJJJR3n36jfh229sdWBxyl8BhwhH1e1DEv78\/aiL6ckKFPVNzx01R6yDFt3TgEMR97YZ\/R6lWiXG+dsJ305jNFlusLu518zBUvl7g5yjzGN778H29b2C8VLZKmi\/h1CH9jGdD72fCqCYdenD2uZKzb6dsUtXtvBmVcVT6BUGz41W1pkkEEB+YJpMrHILIxAiHRGv1+aZa9\/Oz8LWFd+BEUQjC2LJgojPnpzaG\/msw1nBkX16NNVDWWtJ25Bc\/r\/mG46rwjWB\/cmV6Lwt6KODiqlxgrC4lm9ALOCEWw+23OcYhLwNfQTYevXqHqsFfXOkhUnM8z5vDUb\/HBraB1DjFXN8iLK+1YewD4P495e+SRzrR79Oi3F8SEqRIzRLfN2rnW1BTms\/wYsz0p67cup1Slk1XlNmHwbWX25NVd2PPlLOvZRGoqcKFpIjC5few8THiZfyjiNFwt3RM0AFdZcXY=", + "certificate": "-----BEGIN CERTIFICATE-----\r\nMIIE8jCCAtoCAhAIMA0GCSqGSIb3DQEBCwUAMG0xCzAJBgNVBAYTAlVTMQ8wDQYD\r\nVQQIDAZCb3N0b24xFjAUBgNVBAoMDW93bkNsb3VkIEluYy4xNTAzBgNVBAMMLG93\r\nbkNsb3VkIENvZGUgU2lnbmluZyBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MB4XDTE2\r\nMDUxODA5MzIwMFoXDTI2MDUxNjA5MzIwMFowEDEOMAwGA1UEAwwFdGFza3MwggIi\r\nMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDwgaK\/DswWQalQs8RE9\/5dHk\/h\r\nRWS\/Jw0Wqh2ASaY+EDXw1Nt62GItiEjQ6R1CgrW4RHeL2g5yokbokYD9Wl3JeIbW\r\nv1FcfBuoEBriNQOUmTpFFH5qyR6tlBOzp3uecEF8KcmRsFF\/KlhQ+jNh2rj1q\/Tm\r\nMLvzJFnRhNbW7HNfl0TZjp0O5xtFdoOUimctIUyhUvunH2OY+ySZpdg\/Kqab\/uMQ\r\nLU2qBydj2nsV3HYwiKw2JvzEFxMQ4DGPbTbPVBT6RXEL\/yD8kWjDFzQLz+I0bkpV\r\nPoTy\/7LCX2xUlMTTCxRaIbvLpzKxlBkD9v66JhijF3zVVUhU8yslxCMKdNNvxMOH\r\n3IYnrND762pakq+UCv+nvdr39tGXUaEyqUVjWX7SoY56uIU\/wR3ny9NNuCacozGg\r\n81lPrVnBPv7NSD7eSkQvf5V2yp9BneZsvVkgiuWxB9PG2XmHMCbmG\/1I730pWEb+\r\nxm8q7MdXBf+2VWlP4aZiDDI3c\/tdO+kEiivPMpkf8aNNaFS\/QuC0jr7ZyMHhPxSK\r\nZ0lO00fca\/fyX0qv9T6EpHOoVrn2cN2z8Atot2iGk11N\/nzVv3gzMQQOCTSO+67i\r\nRN6BxqcmQIbsoLIa35nDkpzZH3ob8cCmrhAMLxVdS08o5fZ4uCzuBVp4ntjCLbrM\r\nVBfJrg82cOrkzLpzhQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQCqVh0ZzU3UZ3tg\r\nsc8jDI+MMrUU6A1gUv0zmT4yWYi2PZwWuhJ5V5z9GftZZNl8AmeyvaDRPdAFP0x4\r\nD6MIUthG9TIfu4b1bRbj1W29U+7xFF3A0B8zuLtRlokvjYjhY+PLx6NHh1L+pkKq\r\n8G87+PXz2N9eSuf\/6Mx7Xgg\/xScfpVDzLRmHgwSczXvyzMRT66HrNeZZBn6bNckC\r\nXhfurg1oZmYR2lkhZLPEB3p5ZtNWEYmsdyQz9N\/J0SrDwcSeUXI4M6X1mCf4D7rX\r\nHRTeV5lH3VEQ+FfSL1mqDyRHCU3TKPzVjKNFHrk8XsnwZlcryWkgRodwHhVKZAeU\r\no2JmrmDGUMvJ3ktngI2TNGq99eYe3+lM4axFxr8VryRGfFu+0cR0x4ECasaHdLTy\r\nttitTcZ3+FCGTYCkhfWc0K2GegZzJiuMZ\/Culm+tvwX4Z9fH1caWKDI55rk2SULD\r\nuCjh94RGxlRKmgljQPVN\/buFDNE+x+Is18APa\/5YExQqvfVsRsQ72wk+pzttFdAr\r\nDQclXYVjITPOgmX7l654rw7CGUi1lNFAWf+O7psnwEvF3ytPbaYlqWQJlnaYByN8\r\neE5bAMBkEoDV2eLmJN4F4R0KQThUDy6dvK2XlI0HUbDZgMZbWMz+D3Fv54ZTRMaW\r\nn3MEtya90V9SVUbYcwp7dhF\/FVM3ug==\r\n-----END CERTIFICATE-----" +}'; + $this->fileAccessHelper + ->expects($this->at(0)) + ->method('file_get_contents') + ->with( + \OC::$SERVERROOT . '/tests/data/integritycheck/app//appinfo/signature.json' + ) + ->will($this->returnValue($signatureDataFile)); + $this->fileAccessHelper + ->expects($this->at(1)) + ->method('file_get_contents') + ->with( + '/resources/codesigning/root.crt' + ) + ->will($this->returnValue(file_get_contents(__DIR__ .'/../../../resources/codesigning/root.crt'))); + $this->fileAccessHelper + ->expects($this->at(2)) + ->method('file_get_contents') + ->with( + '/resources/codesigning/intermediate.crl.pem' + ) + ->will($this->returnValue(file_get_contents(__DIR__ .'/../../../resources/codesigning/intermediate.crl.pem'))); + $expected = [ + 'EXCEPTION' => [ + 'class' => 'OC\\IntegrityCheck\\Exceptions\\InvalidSignatureException', + 'message' => 'Certificate has been revoked.', + ] + ]; + + $this->assertSame($expected, $this->checker->verifyAppSignature('SomeApp')); + } + } -- cgit v1.2.3