From ecdbf006284fa4907b54f58ac6ba59f54b9738a5 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 14 Aug 2014 14:24:10 +0200 Subject: [PATCH 01/18] Move certificate management code to core --- .../ajax/addRootCertificate.php | 34 ++--- .../ajax/removeRootCertificate.php | 10 +- apps/files_external/lib/config.php | 47 ------- apps/files_external/personal.php | 3 +- lib/private/certificatemanager.php | 116 ++++++++++++++++++ lib/private/server.php | 17 +++ lib/public/icertificatemanager.php | 40 ++++++ lib/public/iservercontainer.php | 7 ++ 8 files changed, 193 insertions(+), 81 deletions(-) create mode 100644 lib/private/certificatemanager.php create mode 100644 lib/public/icertificatemanager.php diff --git a/apps/files_external/ajax/addRootCertificate.php b/apps/files_external/ajax/addRootCertificate.php index fcd3a617ad..38b1860594 100644 --- a/apps/files_external/ajax/addRootCertificate.php +++ b/apps/files_external/ajax/addRootCertificate.php @@ -3,8 +3,8 @@ OCP\JSON::checkAppEnabled('files_external'); OCP\JSON::callCheck(); -if ( ! ($filename = $_FILES['rootcert_import']['name']) ) { - header('Location:' . OCP\Util::linkToRoute( "settings_personal" )); +if (!($filename = $_FILES['rootcert_import']['name'])) { + header('Location:' . OCP\Util::linkToRoute("settings_personal")); exit; } @@ -13,33 +13,13 @@ $data = fread($fh, filesize($_FILES['rootcert_import']['tmp_name'])); fclose($fh); $filename = $_FILES['rootcert_import']['name']; -$view = new \OC\Files\View('/'.\OCP\User::getUser().'/files_external/uploads'); -if (!$view->file_exists('')) { - $view->mkdir(''); -} +$certificateManager = \OC::$server->getCertificateManager(); -$isValid = openssl_pkey_get_public($data); - -//maybe it was just the wrong file format, try to convert it... -if ($isValid == false) { - $data = chunk_split(base64_encode($data), 64, "\n"); - $data = "-----BEGIN CERTIFICATE-----\n".$data."-----END CERTIFICATE-----\n"; - $isValid = openssl_pkey_get_public($data); -} - -// add the certificate if it could be verified -if ( $isValid ) { - // disable proxy to prevent multiple fopen calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - $view->file_put_contents($filename, $data); - OC_Mount_Config::createCertificateBundle(); - \OC_FileProxy::$enabled = $proxyStatus; -} else { +if (!$certificateManager->addCertificate($data, $filename)) { OCP\Util::writeLog('files_external', - 'Couldn\'t import SSL root certificate ('.$filename.'), allowed formats: PEM and DER', - OCP\Util::WARN); + 'Couldn\'t import SSL root certificate (' . $filename . '), allowed formats: PEM and DER', + OCP\Util::WARN); } -header('Location:' . OCP\Util::linkToRoute( "settings_personal" )); +header('Location:' . OCP\Util::linkToRoute("settings_personal")); exit; diff --git a/apps/files_external/ajax/removeRootCertificate.php b/apps/files_external/ajax/removeRootCertificate.php index 664b3937e9..e6795800e0 100644 --- a/apps/files_external/ajax/removeRootCertificate.php +++ b/apps/files_external/ajax/removeRootCertificate.php @@ -4,10 +4,8 @@ OCP\JSON::checkAppEnabled('files_external'); OCP\JSON::checkLoggedIn(); OCP\JSON::callCheck(); -$view = \OCP\Files::getStorage("files_external"); -$file = 'uploads/'.ltrim($_POST['cert'], "/\\."); - -if ( $view->file_exists($file) ) { - $view->unlink($file); - OC_Mount_Config::createCertificateBundle(); +$name = $_POST['cert']; +$certificateManager = \OC::$server->getCertificateManager(); +if (\OC\Files\Filesystem::isValidPath($name)) { + $certificateManager->removeCertificate($name); } diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index 85e36fd904..952463b801 100755 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -619,53 +619,6 @@ class OC_Mount_Config { @chmod($file, 0640); } - /** - * Returns all user uploaded ssl root certificates - * @return array - */ - public static function getCertificates() { - $path=OC_User::getHome(OC_User::getUser()) . '/files_external/uploads/'; - \OCP\Util::writeLog('files_external', 'checking path '.$path, \OCP\Util::INFO); - if ( ! is_dir($path)) { - //path might not exist (e.g. non-standard OC_User::getHome() value) - //in this case create full path using 3rd (recursive=true) parameter. - mkdir($path, 0777, true); - } - $result = array(); - $handle = opendir($path); - if(!is_resource($handle)) { - return array(); - } - while (false !== ($file = readdir($handle))) { - if ($file != '.' && $file != '..') $result[] = $file; - } - return $result; - } - - /** - * creates certificate bundle - */ - public static function createCertificateBundle() { - $path=OC_User::getHome(OC_User::getUser()) . '/files_external'; - - $certs = OC_Mount_Config::getCertificates(); - $fh_certs = fopen($path."/rootcerts.crt", 'w'); - foreach ($certs as $cert) { - $file=$path.'/uploads/'.$cert; - $fh = fopen($file, "r"); - $data = fread($fh, filesize($file)); - fclose($fh); - if (strpos($data, 'BEGIN CERTIFICATE')) { - fwrite($fh_certs, $data); - fwrite($fh_certs, "\r\n"); - } - } - - fclose($fh_certs); - - return true; - } - /** * check dependencies */ diff --git a/apps/files_external/personal.php b/apps/files_external/personal.php index 90d7afed28..9965303f21 100755 --- a/apps/files_external/personal.php +++ b/apps/files_external/personal.php @@ -23,11 +23,12 @@ OCP\Util::addScript('files_external', 'settings'); OCP\Util::addStyle('files_external', 'settings'); $backends = OC_Mount_Config::getPersonalBackends(); +$certificateManager = \OC::$server->getCertificateManager(); $tmpl = new OCP\Template('files_external', 'settings'); $tmpl->assign('isAdminPage', false); $tmpl->assign('mounts', OC_Mount_Config::getPersonalMountPoints()); -$tmpl->assign('certs', OC_Mount_Config::getCertificates()); +$tmpl->assign('certs', $certificateManager->listCertificates()); $tmpl->assign('dependencies', OC_Mount_Config::checkDependencies()); $tmpl->assign('backends', $backends); return $tmpl->fetchPage(); diff --git a/lib/private/certificatemanager.php b/lib/private/certificatemanager.php new file mode 100644 index 0000000000..72e0541fa4 --- /dev/null +++ b/lib/private/certificatemanager.php @@ -0,0 +1,116 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC; + +/** + * Manage trusted certificates for users + */ +class CertificateManager { + /** + * @var \OCP\IUser + */ + protected $user; + + /** + * @param \OCP\IUser $user + */ + public function __construct($user) { + $this->user = $user; + } + + /** + * Returns all certificates trusted by the user + * + * @return string[] + */ + public function listCertificates() { + $path = $this->user->getHome() . '/files_external/uploads/'; + if (!is_dir($path)) { + //path might not exist (e.g. non-standard OC_User::getHome() value) + //in this case create full path using 3rd (recursive=true) parameter. + //note that we use "normal" php filesystem functions here since the certs need to be local + mkdir($path, 0777, true); + } + $result = array(); + $handle = opendir($path); + if (!is_resource($handle)) { + return array(); + } + while (false !== ($file = readdir($handle))) { + if ($file != '.' && $file != '..') $result[] = $file; + } + return $result; + } + + /** + * create the certificate bundle of all trusted certificated + */ + protected function createCertificateBundle() { + $path = $this->user->getHome() . '/files_external/'; + $certs = $this->listCertificates(); + + $fh_certs = fopen($path . '/rootcerts.crt', 'w'); + foreach ($certs as $cert) { + $file = $path . '/uploads/' . $cert; + $fh = fopen($file, 'r'); + $data = fread($fh, filesize($file)); + fclose($fh); + if (strpos($data, 'BEGIN CERTIFICATE')) { + fwrite($fh_certs, $data); + fwrite($fh_certs, "\r\n"); + } + } + + fclose($fh_certs); + } + + /** + * @param string $certificate the certificate data + * @param string $name the filename for the certificate + * @return bool + */ + public function addCertificate($certificate, $name) { + $isValid = openssl_pkey_get_public($certificate); + + if (!$isValid) { + $data = chunk_split(base64_encode($certificate), 64, "\n"); + $data = "-----BEGIN CERTIFICATE-----\n" . $data . "-----END CERTIFICATE-----\n"; + $isValid = openssl_pkey_get_public($data); + } + + if ($isValid) { + $file = $this->user->getHome() . '/files_external/uploads/' . $name; + file_put_contents($file, $certificate); + $this->createCertificateBundle(); + return true; + } else { + return false; + } + } + + /** + * @param string $name + */ + public function removeCertificate($name) { + $path = $this->user->getHome() . '/files_external/uploads/'; + if (file_exists($path . $name)) { + unlink($path . $name); + $this->createCertificateBundle(); + } + } + + /** + * Get the path to the certificate bundle for this user + * + * @return string + */ + public function getCertificateBundle() { + return $this->user->getHome() . '/files_external/rootcerts.crt'; + } +} diff --git a/lib/private/server.php b/lib/private/server.php index 53aab7a586..a30571c1e1 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -469,4 +469,21 @@ class Server extends SimpleContainer implements IServerContainer { function getDb() { return $this->query('Db'); } + + /** + * Get the certificate manager for the user + * + * @param \OCP\IUser $user (optional) if not specified the current loggedin user is used + * @return \OCP\ICertificateManager + */ + function getCertificateManager($user = null) { + if (is_null($user)) { + $userSession = $this->getUserSession(); + $user = $userSession->getUser(); + if (is_null($user)) { + return null; + } + } + return new CertificateManager($user); + } } diff --git a/lib/public/icertificatemanager.php b/lib/public/icertificatemanager.php new file mode 100644 index 0000000000..9ce7d72117 --- /dev/null +++ b/lib/public/icertificatemanager.php @@ -0,0 +1,40 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCP; + +/** + * Manage trusted certificates for users + */ +interface ICertificateManager { + /** + * Returns all certificates trusted by the user + * + * @return string[] + */ + public function listCertificates(); + + /** + * @param string $certificate the certificate data + * @param string $name the filename for the certificate + * @return bool + */ + public function addCertificate($certificate, $name); + + /** + * @param string $name + */ + public function removeCertificate($name); + + /** + * Get the path to the certificate bundle for this user + * + * @return string + */ + public function getCertificateBundle(); +} diff --git a/lib/public/iservercontainer.php b/lib/public/iservercontainer.php index 9c39ac7ae7..215bf92f39 100644 --- a/lib/public/iservercontainer.php +++ b/lib/public/iservercontainer.php @@ -226,4 +226,11 @@ interface IServerContainer { */ function getSearch(); + /** + * Get the certificate manager for the user + * + * @param \OCP\IUser $user (optional) if not specified the current loggedin user is used + * @return \OCP\ICertificateManager + */ + function getCertificateManager($user = null); } From c2ebc192a6a6c7cb9cff958e412e0cc4aff15ead Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 14 Aug 2014 14:24:56 +0200 Subject: [PATCH 02/18] Make external shares work with imported self-signed certificates --- apps/files_sharing/lib/external/manager.php | 4 +++- apps/files_sharing/lib/external/storage.php | 12 +++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/files_sharing/lib/external/manager.php b/apps/files_sharing/lib/external/manager.php index dda283f495..8176302a86 100644 --- a/apps/files_sharing/lib/external/manager.php +++ b/apps/files_sharing/lib/external/manager.php @@ -113,9 +113,11 @@ class Manager { * @return Mount */ protected function mountShare($data) { + $user = $this->userSession->getUser(); $data['manager'] = $this; - $mountPoint = '/' . $this->userSession->getUser()->getUID() . '/files' . $data['mountpoint']; + $mountPoint = '/' . $user->getUID() . '/files' . $data['mountpoint']; $data['mountpoint'] = $mountPoint; + $data['certificateManager'] = \OC::$server->getCertificateManager($user); $mount = new Mount(self::STORAGE, $mountPoint, $data, $this, $this->storageLoader); $this->mountManager->addMount($mount); return $mount; diff --git a/apps/files_sharing/lib/external/storage.php b/apps/files_sharing/lib/external/storage.php index 855be2872b..b3a8bdcc96 100644 --- a/apps/files_sharing/lib/external/storage.php +++ b/apps/files_sharing/lib/external/storage.php @@ -37,6 +37,11 @@ class Storage extends DAV implements ISharedStorage { */ private $token; + /** + * @var \OCP\ICertificateManager + */ + private $certificateManager; + private $updateChecked = false; /** @@ -46,6 +51,7 @@ class Storage extends DAV implements ISharedStorage { public function __construct($options) { $this->manager = $options['manager']; + $this->certificateManager = $options['certificateManager']; $this->remote = $options['remote']; $this->remoteUser = $options['owner']; list($protocol, $remote) = explode('://', $this->remote); @@ -190,7 +196,11 @@ class Storage extends DAV implements ISharedStorage { http_build_query(array('password' => $password))); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - $result = curl_exec($ch); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_CAINFO, $this->certificateManager->getCertificateBundle()); + + $result = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); From dcc4f96d3e368ee976cd085c18dcb74ada3617ce Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 14 Aug 2014 15:47:23 +0200 Subject: [PATCH 03/18] Verify names of certificates --- apps/files_external/ajax/removeRootCertificate.php | 4 +--- lib/private/certificatemanager.php | 11 ++++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/files_external/ajax/removeRootCertificate.php b/apps/files_external/ajax/removeRootCertificate.php index e6795800e0..0931138ad4 100644 --- a/apps/files_external/ajax/removeRootCertificate.php +++ b/apps/files_external/ajax/removeRootCertificate.php @@ -6,6 +6,4 @@ OCP\JSON::callCheck(); $name = $_POST['cert']; $certificateManager = \OC::$server->getCertificateManager(); -if (\OC\Files\Filesystem::isValidPath($name)) { - $certificateManager->removeCertificate($name); -} +$certificateManager->removeCertificate($name); diff --git a/lib/private/certificatemanager.php b/lib/private/certificatemanager.php index 72e0541fa4..e07413c127 100644 --- a/lib/private/certificatemanager.php +++ b/lib/private/certificatemanager.php @@ -8,10 +8,12 @@ namespace OC; +use OCP\ICertificateManager; + /** * Manage trusted certificates for users */ -class CertificateManager { +class CertificateManager implements ICertificateManager { /** * @var \OCP\IUser */ @@ -76,6 +78,9 @@ class CertificateManager { * @return bool */ public function addCertificate($certificate, $name) { + if (!\OC\Files\Filesystem::isValidPath($name)) { + return false; + } $isValid = openssl_pkey_get_public($certificate); if (!$isValid) { @@ -96,8 +101,12 @@ class CertificateManager { /** * @param string $name + * @return bool */ public function removeCertificate($name) { + if (!\OC\Files\Filesystem::isValidPath($name)) { + return false; + } $path = $this->user->getHome() . '/files_external/uploads/'; if (file_exists($path . $name)) { unlink($path . $name); From 4efdbff6dfe20329db145617ed38aa23391b1860 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 14 Aug 2014 15:47:58 +0200 Subject: [PATCH 04/18] No need for this folder to be world accessible --- lib/private/certificatemanager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/certificatemanager.php b/lib/private/certificatemanager.php index e07413c127..3a93494578 100644 --- a/lib/private/certificatemanager.php +++ b/lib/private/certificatemanager.php @@ -37,7 +37,7 @@ class CertificateManager implements ICertificateManager { //path might not exist (e.g. non-standard OC_User::getHome() value) //in this case create full path using 3rd (recursive=true) parameter. //note that we use "normal" php filesystem functions here since the certs need to be local - mkdir($path, 0777, true); + mkdir($path, 0700, true); } $result = array(); $handle = opendir($path); From 298011bf296a4eda62b787bb7c8fbfe30644d488 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 14 Aug 2014 15:49:31 +0200 Subject: [PATCH 05/18] fix indention --- apps/files_sharing/lib/external/storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_sharing/lib/external/storage.php b/apps/files_sharing/lib/external/storage.php index b3a8bdcc96..92d8f92b38 100644 --- a/apps/files_sharing/lib/external/storage.php +++ b/apps/files_sharing/lib/external/storage.php @@ -200,7 +200,7 @@ class Storage extends DAV implements ISharedStorage { curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($ch, CURLOPT_CAINFO, $this->certificateManager->getCertificateBundle()); - $result = curl_exec($ch); + $result = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); From c1b11571ea53748d57241598dec71750637416cd Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 15 Aug 2014 17:18:46 +0200 Subject: [PATCH 06/18] Move certificate management interface from files_external to core --- .../ajax/addRootCertificate.php | 25 -- apps/files_external/personal.php | 2 - apps/files_external/templates/settings.php | 27 --- lib/private/certificatemanager.php | 10 +- lib/private/l10n.php | 2 +- lib/public/icertificatemanager.php | 4 +- settings/ajax/addRootCertificate.php | 34 +++ .../ajax/removeRootCertificate.php | 0 settings/css/settings.css | 8 +- settings/js/personal.js | 215 +++++++++++------- settings/personal.php | 2 + settings/routes.php | 4 + settings/templates/personal.php | 41 +++- 13 files changed, 220 insertions(+), 154 deletions(-) delete mode 100644 apps/files_external/ajax/addRootCertificate.php create mode 100644 settings/ajax/addRootCertificate.php rename {apps/files_external => settings}/ajax/removeRootCertificate.php (100%) diff --git a/apps/files_external/ajax/addRootCertificate.php b/apps/files_external/ajax/addRootCertificate.php deleted file mode 100644 index 38b1860594..0000000000 --- a/apps/files_external/ajax/addRootCertificate.php +++ /dev/null @@ -1,25 +0,0 @@ -getCertificateManager(); - -if (!$certificateManager->addCertificate($data, $filename)) { - OCP\Util::writeLog('files_external', - 'Couldn\'t import SSL root certificate (' . $filename . '), allowed formats: PEM and DER', - OCP\Util::WARN); -} - -header('Location:' . OCP\Util::linkToRoute("settings_personal")); -exit; diff --git a/apps/files_external/personal.php b/apps/files_external/personal.php index 9965303f21..a279163ff7 100755 --- a/apps/files_external/personal.php +++ b/apps/files_external/personal.php @@ -23,12 +23,10 @@ OCP\Util::addScript('files_external', 'settings'); OCP\Util::addStyle('files_external', 'settings'); $backends = OC_Mount_Config::getPersonalBackends(); -$certificateManager = \OC::$server->getCertificateManager(); $tmpl = new OCP\Template('files_external', 'settings'); $tmpl->assign('isAdminPage', false); $tmpl->assign('mounts', OC_Mount_Config::getPersonalMountPoints()); -$tmpl->assign('certs', $certificateManager->listCertificates()); $tmpl->assign('dependencies', OC_Mount_Config::checkDependencies()); $tmpl->assign('backends', $backends); return $tmpl->fetchPage(); diff --git a/apps/files_external/templates/settings.php b/apps/files_external/templates/settings.php index dd283f9ff5..072f856dfb 100644 --- a/apps/files_external/templates/settings.php +++ b/apps/files_external/templates/settings.php @@ -119,30 +119,3 @@

- - -
-

t('SSL root certificates'));?>

- '> - - - - - - - - -
class="remove" - style="visibility:hidden;" - ><?php p($l->t('Delete')); ?>
- - - -
- diff --git a/lib/private/certificatemanager.php b/lib/private/certificatemanager.php index 3a93494578..90a30182c6 100644 --- a/lib/private/certificatemanager.php +++ b/lib/private/certificatemanager.php @@ -29,7 +29,7 @@ class CertificateManager implements ICertificateManager { /** * Returns all certificates trusted by the user * - * @return string[] + * @return \OCP\ICertificate[] */ public function listCertificates() { $path = $this->user->getHome() . '/files_external/uploads/'; @@ -45,7 +45,9 @@ class CertificateManager implements ICertificateManager { return array(); } while (false !== ($file = readdir($handle))) { - if ($file != '.' && $file != '..') $result[] = $file; + if ($file != '.' && $file != '..') { + $result[] = new Certificate(file_get_contents($path . $file), $file); + } } return $result; } @@ -75,7 +77,7 @@ class CertificateManager implements ICertificateManager { /** * @param string $certificate the certificate data * @param string $name the filename for the certificate - * @return bool + * @return bool | \OCP\ICertificate */ public function addCertificate($certificate, $name) { if (!\OC\Files\Filesystem::isValidPath($name)) { @@ -93,7 +95,7 @@ class CertificateManager implements ICertificateManager { $file = $this->user->getHome() . '/files_external/uploads/' . $name; file_put_contents($file, $certificate); $this->createCertificateBundle(); - return true; + return new Certificate($certificate, $name); } else { return false; } diff --git a/lib/private/l10n.php b/lib/private/l10n.php index 28b35e92a2..57886a796c 100644 --- a/lib/private/l10n.php +++ b/lib/private/l10n.php @@ -354,7 +354,7 @@ class OC_L10N implements \OCP\IL10N { case 'datetime': case 'time': if($data instanceof DateTime) { - return $data->format($this->localizations[$type]); + $data = $data->getTimestamp(); } elseif(is_string($data) && !is_numeric($data)) { $data = strtotime($data); } diff --git a/lib/public/icertificatemanager.php b/lib/public/icertificatemanager.php index 9ce7d72117..24b8d12363 100644 --- a/lib/public/icertificatemanager.php +++ b/lib/public/icertificatemanager.php @@ -15,14 +15,14 @@ interface ICertificateManager { /** * Returns all certificates trusted by the user * - * @return string[] + * @return \OCP\ICertificate[] */ public function listCertificates(); /** * @param string $certificate the certificate data * @param string $name the filename for the certificate - * @return bool + * @return bool | \OCP\ICertificate */ public function addCertificate($certificate, $name); diff --git a/settings/ajax/addRootCertificate.php b/settings/ajax/addRootCertificate.php new file mode 100644 index 0000000000..9be8fd0025 --- /dev/null +++ b/settings/ajax/addRootCertificate.php @@ -0,0 +1,34 @@ +getCertificateManager(); + +if ($cert = $certificateManager->addCertificate($data, $filename)) { + OCP\JSON::success(array( + 'name' => $cert->getName(), + 'commonName' => $cert->getCommonName(), + 'organization' => $cert->getOrganization(), + 'validFrom' => $cert->getIssueDate()->getTimestamp(), + 'validTill' => $cert->getExpireDate()->getTimestamp(), + 'validFromString' => $l->l('date', $cert->getIssueDate()), + 'validTillString' => $l->l('date', $cert->getExpireDate()), + 'issuer' => $cert->getIssuerName(), + 'issuerOrganization' => $cert->getIssuerOrganization() + )); +} else { + OCP\JSON::error(array('error' => 'Couldn\'t import SSL root certificate, allowed formats: PEM and DER')); +} diff --git a/apps/files_external/ajax/removeRootCertificate.php b/settings/ajax/removeRootCertificate.php similarity index 100% rename from apps/files_external/ajax/removeRootCertificate.php rename to settings/ajax/removeRootCertificate.php diff --git a/settings/css/settings.css b/settings/css/settings.css index a62a971b83..95fab85df9 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -6,9 +6,11 @@ select#languageinput, select#timezone { width:15em; } input#openid, input#webdav { width:20em; } /* PERSONAL */ -#rootcert_import { - margin: 0 0 10px 0; - display: block; +#sslCertificate tr.expired { + background-color: rgba(255, 0, 0, 0.5); +} +#sslCertificate td { + padding: 5px; } /* Sync clients */ diff --git a/settings/js/personal.js b/settings/js/personal.js index f56dd3425f..d6763cdb29 100644 --- a/settings/js/personal.js +++ b/settings/js/personal.js @@ -13,12 +13,12 @@ * * @param callback */ -jQuery.fn.keyUpDelayedOrEnter = function(callback){ +jQuery.fn.keyUpDelayedOrEnter = function (callback) { var cb = callback; var that = this; this.keyup(_.debounce(function (event) { // enter is already handled in keypress - if(event.keyCode === 13) { + if (event.keyCode === 13) { return; } if (that.val() !== '') { @@ -27,7 +27,7 @@ jQuery.fn.keyUpDelayedOrEnter = function(callback){ }, 1000)); this.keypress(function (event) { - if (event.keyCode === 13 && that.val() !== '' ){ + if (event.keyCode === 13 && that.val() !== '') { event.preventDefault(); cb(); } @@ -38,48 +38,48 @@ jQuery.fn.keyUpDelayedOrEnter = function(callback){ /** * Post the email address change to the server. */ -function changeEmailAddress(){ - var emailInfo = $('#email'); - if (emailInfo.val() === emailInfo.defaultValue){ - return; - } - emailInfo.defaultValue = emailInfo.val(); - OC.msg.startSaving('#lostpassword .msg'); - var post = $( "#lostpassword" ).serialize(); - $.post( 'ajax/lostpassword.php', post, function(data){ - OC.msg.finishedSaving('#lostpassword .msg', data); - }); +function changeEmailAddress () { + var emailInfo = $('#email'); + if (emailInfo.val() === emailInfo.defaultValue) { + return; + } + emailInfo.defaultValue = emailInfo.val(); + OC.msg.startSaving('#lostpassword .msg'); + var post = $("#lostpassword").serialize(); + $.post('ajax/lostpassword.php', post, function (data) { + OC.msg.finishedSaving('#lostpassword .msg', data); + }); } /** * Post the display name change to the server. */ -function changeDisplayName(){ - if ($('#displayName').val() !== '' ) { - OC.msg.startSaving('#displaynameform .msg'); - // Serialize the data - var post = $( "#displaynameform" ).serialize(); - // Ajax foo - $.post( 'ajax/changedisplayname.php', post, function(data){ - if( data.status === "success" ){ - $('#oldDisplayName').val($('#displayName').val()); - // update displayName on the top right expand button - $('#expandDisplayName').text($('#displayName').val()); - updateAvatar(); - } - else{ - $('#newdisplayname').val(data.data.displayName); - } - OC.msg.finishedSaving('#displaynameform .msg', data); - }); - } +function changeDisplayName () { + if ($('#displayName').val() !== '') { + OC.msg.startSaving('#displaynameform .msg'); + // Serialize the data + var post = $("#displaynameform").serialize(); + // Ajax foo + $.post('ajax/changedisplayname.php', post, function (data) { + if (data.status === "success") { + $('#oldDisplayName').val($('#displayName').val()); + // update displayName on the top right expand button + $('#expandDisplayName').text($('#displayName').val()); + updateAvatar(); + } + else { + $('#newdisplayname').val(data.data.displayName); + } + OC.msg.finishedSaving('#displaynameform .msg', data); + }); + } } function updateAvatar (hidedefault) { var $headerdiv = $('#header .avatardiv'); var $displaydiv = $('#displayavatar .avatardiv'); - if(hidedefault) { + if (hidedefault) { $headerdiv.hide(); $('#header .avatardiv').removeClass('avatardiv-shown'); } else { @@ -93,16 +93,16 @@ function updateAvatar (hidedefault) { $('#removeavatar').show(); } -function showAvatarCropper() { +function showAvatarCropper () { var $cropper = $('#cropper'); $cropper.prepend(""); var $cropperImage = $('#cropper img'); $cropperImage.attr('src', - OC.generateUrl('/avatar/tmp')+'?requesttoken='+oc_requesttoken+'#'+Math.floor(Math.random()*1000)); + OC.generateUrl('/avatar/tmp') + '?requesttoken=' + oc_requesttoken + '#' + Math.floor(Math.random() * 1000)); // Looks weird, but on('load', ...) doesn't work in IE8 - $cropperImage.ready(function(){ + $cropperImage.ready(function () { $('#displayavatar').hide(); $cropper.show(); @@ -117,7 +117,7 @@ function showAvatarCropper() { }); } -function sendCropData() { +function sendCropData () { cleanCropper(); var cropperData = $('#cropper').data(); @@ -130,11 +130,11 @@ function sendCropData() { $.post(OC.generateUrl('/avatar/cropped'), {crop: data}, avatarResponseHandler); } -function saveCoords(c) { +function saveCoords (c) { $('#cropper').data(c); } -function cleanCropper() { +function cleanCropper () { var $cropper = $('#cropper'); $('#displayavatar').show(); $cropper.hide(); @@ -143,7 +143,7 @@ function cleanCropper() { $('#cropper img').remove(); } -function avatarResponseHandler(data) { +function avatarResponseHandler (data) { var $warning = $('#avatar .warning'); $warning.hide(); if (data.status === "success") { @@ -156,20 +156,20 @@ function avatarResponseHandler(data) { } } -$(document).ready(function(){ - $("#passwordbutton").click( function(){ +$(document).ready(function () { + $("#passwordbutton").click(function () { if ($('#pass1').val() !== '' && $('#pass2').val() !== '') { // Serialize the data - var post = $( "#passwordform" ).serialize(); + var post = $("#passwordform").serialize(); $('#passwordchanged').hide(); $('#passworderror').hide(); // Ajax foo - $.post(OC.generateUrl('/settings/personal/changepassword'), post, function(data){ - if( data.status === "success" ){ + $.post(OC.generateUrl('/settings/personal/changepassword'), post, function (data) { + if (data.status === "success") { $('#pass1').val(''); $('#pass2').val(''); $('#passwordchanged').show(); - } else{ + } else { if (typeof(data.data) !== "undefined") { $('#passworderror').html(data.data.message); } else { @@ -190,22 +190,22 @@ $(document).ready(function(){ $('#displayName').keyUpDelayedOrEnter(changeDisplayName); $('#email').keyUpDelayedOrEnter(changeEmailAddress); - $("#languageinput").change( function(){ + $("#languageinput").change(function () { // Serialize the data - var post = $( "#languageinput" ).serialize(); + var post = $("#languageinput").serialize(); // Ajax foo - $.post( 'ajax/setlanguage.php', post, function(data){ - if( data.status === "success" ){ + $.post('ajax/setlanguage.php', post, function (data) { + if (data.status === "success") { location.reload(); } - else{ - $('#passworderror').html( data.data.message ); + else { + $('#passworderror').html(data.data.message); } }); return false; }); - $('button:button[name="submitDecryptAll"]').click(function() { + $('button:button[name="submitDecryptAll"]').click(function () { var privateKeyPassword = $('#decryptAll input:password[id="privateKeyPassword"]').val(); $('#decryptAll button:button[name="submitDecryptAll"]').prop("disabled", true); $('#decryptAll input:password[name="privateKeyPassword"]').prop("disabled", true); @@ -213,23 +213,23 @@ $(document).ready(function(){ }); - $('button:button[name="submitRestoreKeys"]').click(function() { + $('button:button[name="submitRestoreKeys"]').click(function () { $('#restoreBackupKeys button:button[name="submitDeleteKeys"]').prop("disabled", true); $('#restoreBackupKeys button:button[name="submitRestoreKeys"]').prop("disabled", true); OC.Encryption.restoreKeys(); }); - $('button:button[name="submitDeleteKeys"]').click(function() { + $('button:button[name="submitDeleteKeys"]').click(function () { $('#restoreBackupKeys button:button[name="submitDeleteKeys"]').prop("disabled", true); $('#restoreBackupKeys button:button[name="submitRestoreKeys"]').prop("disabled", true); OC.Encryption.deleteKeys(); }); - $('#decryptAll input:password[name="privateKeyPassword"]').keyup(function(event) { + $('#decryptAll input:password[name="privateKeyPassword"]').keyup(function (event) { var privateKeyPassword = $('#decryptAll input:password[id="privateKeyPassword"]').val(); - if (privateKeyPassword !== '' ) { + if (privateKeyPassword !== '') { $('#decryptAll button:button[name="submitDecryptAll"]').prop("disabled", false); - if(event.which === 13) { + if (event.which === 13) { $('#decryptAll button:button[name="submitDecryptAll"]').prop("disabled", true); $('#decryptAll input:password[name="privateKeyPassword"]').prop("disabled", true); OC.Encryption.decryptAll(privateKeyPassword); @@ -240,21 +240,21 @@ $(document).ready(function(){ }); var uploadparms = { - done: function(e, data) { + done: function (e, data) { avatarResponseHandler(data.result); } }; - $('#uploadavatarbutton').click(function(){ + $('#uploadavatarbutton').click(function () { $('#uploadavatar').click(); }); $('#uploadavatar').fileupload(uploadparms); - $('#selectavatar').click(function(){ + $('#selectavatar').click(function () { OC.dialogs.filepicker( t('settings', "Select a profile picture"), - function(path){ + function (path) { $.post(OC.generateUrl('/avatar/'), {path: path}, avatarResponseHandler); }, false, @@ -262,27 +262,27 @@ $(document).ready(function(){ ); }); - $('#removeavatar').click(function(){ + $('#removeavatar').click(function () { $.ajax({ - type: 'DELETE', - url: OC.generateUrl('/avatar/'), - success: function() { + type: 'DELETE', + url: OC.generateUrl('/avatar/'), + success: function () { updateAvatar(true); $('#removeavatar').hide(); } }); }); - $('#abortcropperbutton').click(function(){ + $('#abortcropperbutton').click(function () { cleanCropper(); }); - $('#sendcropperbutton').click(function(){ + $('#sendcropperbutton').click(function () { sendCropData(); }); $('#pass2').strengthify({ - zxcvbn: OC.linkTo('3rdparty','zxcvbn/js/zxcvbn.js'), + zxcvbn: OC.linkTo('3rdparty', 'zxcvbn/js/zxcvbn.js'), titles: [ t('core', 'Very weak password'), t('core', 'Weak password'), @@ -298,18 +298,59 @@ $(document).ready(function(){ '/avatar/{user}/{size}', {user: OC.currentUser, size: 1} ) + '?requesttoken=' + oc_requesttoken; - $.get(url, function(result) { + $.get(url, function (result) { if (typeof(result) === 'object') { $('#removeavatar').hide(); } }); -} ); + + $('#sslCertificate').on('click', 'td.remove > img', function () { + var row = $(this).parent().parent(); + $.post(OC.filePath('settings', 'ajax', 'removeRootCertificate.php'), { + cert: row.data('name') + }); + row.remove(); + return true; + }); + + $('#sslCertificate tr > td').tipsy({fade: true, gravity: 'n', live: true}); + + $('#rootcert_import').fileupload({ + done: function (e, data) { + console.log(data.result); + var issueDate = new Date(data.result.validFrom * 1000); + var expireDate = new Date(data.result.validTill * 1000); + var now = new Date(); + var isExpired = !(issueDate <= now && now <= expireDate); + + var row = $(''); + row.addClass(isExpired? 'expired': 'valid'); + row.append($('').attr('title', data.result.organization).text(data.result.commonName)); + row.append($('').attr('title', t('core,', 'Valid from {date}', {date: data.result.validFromString})) + .text(data.result.validTillString)); + row.append($('').attr('title', data.result.issuerOrganization).text(data.result.issuer)); + row.append($('').addClass('remove').append( + $('').attr({ + alt: t('core', 'Delete'), + title: t('core', 'Delete'), + src: OC.imagePath('core', 'actions/delete.svg') + }).addClass('action') + )); + + $('#sslCertificate tbody').append(row); + } + }); + + $('#rootcert_import_button').click(function () { + $('#rootcert_import').click(); + }); +}); OC.Encryption = { - decryptAll: function(password) { + decryptAll: function (password) { var message = t('settings', 'Decrypting files... Please wait, this can take some time.'); OC.Encryption.msg.start('#decryptAll .msg', message); - $.post('ajax/decryptall.php', {password:password}, function(data) { + $.post('ajax/decryptall.php', {password: password}, function (data) { if (data.status === "error") { OC.Encryption.msg.finished('#decryptAll .msg', data); $('#decryptAll input:password[name="privateKeyPassword"]').prop("disabled", false); @@ -320,10 +361,10 @@ OC.Encryption = { }); }, - deleteKeys: function() { + deleteKeys: function () { var message = t('settings', 'Delete encryption keys permanently.'); OC.Encryption.msg.start('#restoreBackupKeys .msg', message); - $.post('ajax/deletekeys.php', null, function(data) { + $.post('ajax/deletekeys.php', null, function (data) { if (data.status === "error") { OC.Encryption.msg.finished('#restoreBackupKeys .msg', data); $('#restoreBackupKeys button:button[name="submitDeleteKeys"]').prop("disabled", false); @@ -334,10 +375,10 @@ OC.Encryption = { }); }, - restoreKeys: function() { + restoreKeys: function () { var message = t('settings', 'Restore encryption keys.'); OC.Encryption.msg.start('#restoreBackupKeys .msg', message); - $.post('ajax/restorekeys.php', {}, function(data) { + $.post('ajax/restorekeys.php', {}, function (data) { if (data.status === "error") { OC.Encryption.msg.finished('#restoreBackupKeys .msg', data); $('#restoreBackupKeys button:button[name="submitDeleteKeys"]').prop("disabled", false); @@ -349,24 +390,24 @@ OC.Encryption = { } }; -OC.Encryption.msg={ - start:function(selector, msg){ - var spinner = ''; +OC.Encryption.msg = { + start: function (selector, msg) { + var spinner = ''; $(selector) - .html( msg + ' ' + spinner ) + .html(msg + ' ' + spinner) .removeClass('success') .removeClass('error') .stop(true, true) .show(); }, - finished:function(selector, data){ - if( data.status === "success" ){ - $(selector).html( data.data.message ) + finished: function (selector, data) { + if (data.status === "success") { + $(selector).html(data.data.message) .addClass('success') .stop(true, true) .delay(3000); - }else{ - $(selector).html( data.data.message ).addClass('error'); + } else { + $(selector).html(data.data.message).addClass('error'); } } }; diff --git a/settings/personal.php b/settings/personal.php index a72b293413..d426fd7f35 100644 --- a/settings/personal.php +++ b/settings/personal.php @@ -8,6 +8,7 @@ OC_Util::checkLoggedIn(); $defaults = new OC_Defaults(); // initialize themable default strings and urls +$certificateManager = \OC::$server->getCertificateManager(); // Highlight navigation entry OC_Util::addScript( 'settings', 'personal' ); @@ -98,6 +99,7 @@ $tmpl->assign('backupKeysExists' , $backupKeysExists); $tmpl->assign('filesStillEncrypted' , $filesStillEncrypted); $tmpl->assign('enableAvatars', \OC_Config::getValue('enable_avatars', true)); $tmpl->assign('avatarChangeSupported', OC_User::canUserChangeAvatar(OC_User::getUser())); +$tmpl->assign('certs', $certificateManager->listCertificates()); $forms=OC_App::getForms('personal'); $tmpl->assign('forms', array()); diff --git a/settings/routes.php b/settings/routes.php index 1c8ad1b3fe..c7bab409d4 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -62,6 +62,10 @@ $this->create('settings_ajax_restorekeys', '/settings/ajax/restorekeys.php') ->actionInclude('settings/ajax/restorekeys.php'); $this->create('settings_ajax_deletekeys', '/settings/ajax/deletekeys.php') ->actionInclude('settings/ajax/deletekeys.php'); +$this->create('core_cert_post', '/settings/ajax/addRootCertificate.php') + ->actionInclude('settings/ajax/addRootCertificate.php'); +$this->create('core_cert_remove', '/settings/ajax/removeRootCertificate.php') + ->actionInclude('settings/ajax/removeRootCertificate.php'); // apps $this->create('settings_ajax_apps_ocs', '/settings/ajax/apps/ocs.php') ->actionInclude('settings/ajax/apps/ocs.php'); diff --git a/settings/templates/personal.php b/settings/templates/personal.php index c1fb20dce0..6d07b89954 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -150,6 +150,44 @@ if($_['passwordChangeSupported']) { print_unescaped($form); };?> +
+

t('SSL root certificates')); ?>

+ + + + + + + + + + + + + + + + +
t('Common Name')); ?>t('Valid until')); ?>t('Issued By')); ?> +
+ getCommonName()) ?> + + l('date', $rootCert->getExpireDate()) ?> + + getIssuerName()) ?> + class="remove" + style="visibility:hidden;" + ><?php p($l->t('Delete')); ?> +
+
+ + +
+
+
@@ -177,11 +215,8 @@ if($_['passwordChangeSupported']) {


- - -
> t( "Your encryption keys are moved to a backup location. If something went wrong you can restore the keys. Only delete them permanently if you are sure that all files are decrypted correctly." )); ?> From c158db72000f0d92c0852abad313e254c4b7fc2e Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 15 Aug 2014 19:54:03 +0200 Subject: [PATCH 07/18] Add certificate class --- lib/private/certificate.php | 116 ++++++++++++++++++++++++++++++++++++ lib/public/icertificate.php | 56 +++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 lib/private/certificate.php create mode 100644 lib/public/icertificate.php diff --git a/lib/private/certificate.php b/lib/private/certificate.php new file mode 100644 index 0000000000..294722bc66 --- /dev/null +++ b/lib/private/certificate.php @@ -0,0 +1,116 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC; + +use OCP\ICertificate; + +class Certificate implements ICertificate { + protected $name; + + protected $commonName; + + protected $organization; + + protected $serial; + + protected $issueDate; + + protected $expireDate; + + protected $issuerName; + + protected $issuerOrganization; + + public function __construct($data, $name) { + $this->name = $name; + $info = openssl_x509_parse($data); + $this->commonName = $info['subject']['CN']; + $this->organization = isset($info['subject']['O']) ? $info['subject']['O'] : null; + $this->serial = $this->formatSerial($info['serialNumber']); + $this->issueDate = new \DateTime('@' . $info['validFrom_time_t']); + $this->expireDate = new \DateTime('@' . $info['validTo_time_t']); + $this->issuerName = $info['issuer']['CN']; + $this->issuerOrganization = isset($info['issuer']['O']) ? $info['issuer']['O'] : null; + } + + /** + * Format the numeric serial into AA:BB:CC hex format + * + * @param int $serial + * @return string + */ + protected function formatSerial($serial) { + $hex = strtoupper(dechex($serial)); + return trim(chunk_split($hex, 2, ':'), ':'); + } + + /** + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * @return string + */ + public function getCommonName() { + return $this->commonName; + } + + /** + * @return string + */ + public function getOrganization() { + return $this->organization; + } + + /** + * @return string + */ + public function getSerial() { + return $this->getSerial(); + } + + /** + * @return \DateTime + */ + public function getIssueDate() { + return $this->issueDate; + } + + /** + * @return \DateTime + */ + public function getExpireDate() { + return $this->expireDate; + } + + /** + * @return bool + */ + public function isExpired() { + $now = new \DateTime(); + return !($this->issueDate <= $now and $now <= $this->expireDate); + } + + /** + * @return string + */ + public function getIssuerName() { + return $this->issuerName; + } + + /** + * @return string + */ + public function getIssuerOrganization() { + return $this->issuerOrganization; + } +} diff --git a/lib/public/icertificate.php b/lib/public/icertificate.php new file mode 100644 index 0000000000..013496cb37 --- /dev/null +++ b/lib/public/icertificate.php @@ -0,0 +1,56 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCP; + +interface ICertificate { + /** + * @return string + */ + public function getName(); + + /** + * @return string + */ + public function getCommonName(); + + /** + * @return string + */ + public function getOrganization(); + + /** + * @return string + */ + public function getSerial(); + + /** + * @return \DateTime + */ + public function getIssueDate(); + + /** + * @return \DateTime + */ + public function getExpireDate(); + + /** + * @return bool + */ + public function isExpired(); + + /** + * @return string + */ + public function getIssuerName(); + + /** + * @return string + */ + public function getIssuerOrganization(); +} From f135130a7524e0b035a721aa395a60117494f806 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 15 Aug 2014 19:56:04 +0200 Subject: [PATCH 08/18] remove outdated checks --- settings/ajax/addRootCertificate.php | 2 -- settings/ajax/removeRootCertificate.php | 2 -- 2 files changed, 4 deletions(-) diff --git a/settings/ajax/addRootCertificate.php b/settings/ajax/addRootCertificate.php index 9be8fd0025..f055a4066e 100644 --- a/settings/ajax/addRootCertificate.php +++ b/settings/ajax/addRootCertificate.php @@ -1,6 +1,4 @@ Date: Mon, 18 Aug 2014 13:43:25 +0200 Subject: [PATCH 09/18] move certificate classes to their own namespace --- lib/private/{ => certificate}/certificate.php | 4 ++-- lib/private/{ => certificate}/certificatemanager.php | 2 +- lib/private/server.php | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) rename lib/private/{ => certificate}/certificate.php (97%) rename lib/private/{ => certificate}/certificatemanager.php (99%) diff --git a/lib/private/certificate.php b/lib/private/certificate/certificate.php similarity index 97% rename from lib/private/certificate.php rename to lib/private/certificate/certificate.php index 294722bc66..801afa7916 100644 --- a/lib/private/certificate.php +++ b/lib/private/certificate/certificate.php @@ -6,7 +6,7 @@ * See the COPYING-README file. */ -namespace OC; +namespace OC\Certificate; use OCP\ICertificate; @@ -75,7 +75,7 @@ class Certificate implements ICertificate { * @return string */ public function getSerial() { - return $this->getSerial(); + return $this->serial; } /** diff --git a/lib/private/certificatemanager.php b/lib/private/certificate/certificatemanager.php similarity index 99% rename from lib/private/certificatemanager.php rename to lib/private/certificate/certificatemanager.php index 90a30182c6..c6207f057d 100644 --- a/lib/private/certificatemanager.php +++ b/lib/private/certificate/certificatemanager.php @@ -6,7 +6,7 @@ * See the COPYING-README file. */ -namespace OC; +namespace OC\Certificate; use OCP\ICertificateManager; diff --git a/lib/private/server.php b/lib/private/server.php index a30571c1e1..311001ce40 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -6,6 +6,7 @@ use OC\AppFramework\Http\Request; use OC\AppFramework\Db\Db; use OC\AppFramework\Utility\SimpleContainer; use OC\Cache\UserCache; +use OC\Certificate\CertificateManager; use OC\DB\ConnectionWrapper; use OC\Files\Node\Root; use OC\Files\View; From 6044ad0e174a0d3c9db174115df9c8f61fd43dc3 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 18 Aug 2014 13:57:38 +0200 Subject: [PATCH 10/18] Cleanup certificate code --- lib/private/certificate/certificate.php | 6 ++++- .../certificate/certificatemanager.php | 26 ++++++++++++------- settings/ajax/addRootCertificate.php | 13 +++++----- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/lib/private/certificate/certificate.php b/lib/private/certificate/certificate.php index 801afa7916..6b4021cf5e 100644 --- a/lib/private/certificate/certificate.php +++ b/lib/private/certificate/certificate.php @@ -27,6 +27,10 @@ class Certificate implements ICertificate { protected $issuerOrganization; + /** + * @param string $data base64 encoded certificate + * @param string $name + */ public function __construct($data, $name) { $this->name = $name; $info = openssl_x509_parse($data); @@ -97,7 +101,7 @@ class Certificate implements ICertificate { */ public function isExpired() { $now = new \DateTime(); - return !($this->issueDate <= $now and $now <= $this->expireDate); + return $this->issueDate > $now or $now > $this->expireDate; } /** diff --git a/lib/private/certificate/certificatemanager.php b/lib/private/certificate/certificatemanager.php index c6207f057d..d7180f7f3f 100644 --- a/lib/private/certificate/certificatemanager.php +++ b/lib/private/certificate/certificatemanager.php @@ -8,6 +8,7 @@ namespace OC\Certificate; +use OC\Files\Filesystem; use OCP\ICertificateManager; /** @@ -34,10 +35,7 @@ class CertificateManager implements ICertificateManager { public function listCertificates() { $path = $this->user->getHome() . '/files_external/uploads/'; if (!is_dir($path)) { - //path might not exist (e.g. non-standard OC_User::getHome() value) - //in this case create full path using 3rd (recursive=true) parameter. - //note that we use "normal" php filesystem functions here since the certs need to be local - mkdir($path, 0700, true); + return array(); } $result = array(); $handle = opendir($path); @@ -62,9 +60,7 @@ class CertificateManager implements ICertificateManager { $fh_certs = fopen($path . '/rootcerts.crt', 'w'); foreach ($certs as $cert) { $file = $path . '/uploads/' . $cert; - $fh = fopen($file, 'r'); - $data = fread($fh, filesize($file)); - fclose($fh); + $data = file_get_contents($file); if (strpos($data, 'BEGIN CERTIFICATE')) { fwrite($fh_certs, $data); fwrite($fh_certs, "\r\n"); @@ -75,6 +71,8 @@ class CertificateManager implements ICertificateManager { } /** + * Save the certificate and re-generate the certificate bundle + * * @param string $certificate the certificate data * @param string $name the filename for the certificate * @return bool | \OCP\ICertificate @@ -92,7 +90,14 @@ class CertificateManager implements ICertificateManager { } if ($isValid) { - $file = $this->user->getHome() . '/files_external/uploads/' . $name; + $dir = $this->user->getHome() . '/files_external/uploads/'; + if (!file_exists($dir)) { + //path might not exist (e.g. non-standard OC_User::getHome() value) + //in this case create full path using 3rd (recursive=true) parameter. + //note that we use "normal" php filesystem functions here since the certs need to be local + mkdir($dir, 0700, true); + } + $file = $dir . $name; file_put_contents($file, $certificate); $this->createCertificateBundle(); return new Certificate($certificate, $name); @@ -102,11 +107,13 @@ class CertificateManager implements ICertificateManager { } /** + * Remove the certificate and re-generate the certificate bundle + * * @param string $name * @return bool */ public function removeCertificate($name) { - if (!\OC\Files\Filesystem::isValidPath($name)) { + if (!Filesystem::isValidPath($name)) { return false; } $path = $this->user->getHome() . '/files_external/uploads/'; @@ -114,6 +121,7 @@ class CertificateManager implements ICertificateManager { unlink($path . $name); $this->createCertificateBundle(); } + return true; } /** diff --git a/settings/ajax/addRootCertificate.php b/settings/ajax/addRootCertificate.php index f055a4066e..87b1460ef1 100644 --- a/settings/ajax/addRootCertificate.php +++ b/settings/ajax/addRootCertificate.php @@ -3,19 +3,18 @@ OCP\JSON::callCheck(); $l = new OC_L10N('core'); -if (!($filename = $_FILES['rootcert_import']['name'])) { - header('Location:' . OCP\Util::linkToRoute("settings_personal")); +if (!isset($_FILES['rootcert_import'])) { + OCP\JSON::error(array('error' => 'No certificate uploaded')); exit; } -$fh = fopen($_FILES['rootcert_import']['tmp_name'], 'r'); -$data = fread($fh, filesize($_FILES['rootcert_import']['tmp_name'])); -fclose($fh); -$filename = $_FILES['rootcert_import']['name']; +$data = file_get_contents($_FILES['rootcert_import']['tmp_name']); +$filename = basename($_FILES['rootcert_import']['name']); $certificateManager = \OC::$server->getCertificateManager(); -if ($cert = $certificateManager->addCertificate($data, $filename)) { +$cert = $certificateManager->addCertificate($data, $filename); +if ($cert) { OCP\JSON::success(array( 'name' => $cert->getName(), 'commonName' => $cert->getCommonName(), From 79d896e830d9076593e1e5366a52cd0061061fed Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 18 Aug 2014 16:30:23 +0200 Subject: [PATCH 11/18] Rename namespace --- lib/private/{certificate => security}/certificate.php | 2 +- lib/private/{certificate => security}/certificatemanager.php | 2 +- lib/private/server.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename lib/private/{certificate => security}/certificate.php (98%) rename lib/private/{certificate => security}/certificatemanager.php (99%) diff --git a/lib/private/certificate/certificate.php b/lib/private/security/certificate.php similarity index 98% rename from lib/private/certificate/certificate.php rename to lib/private/security/certificate.php index 6b4021cf5e..953111f469 100644 --- a/lib/private/certificate/certificate.php +++ b/lib/private/security/certificate.php @@ -6,7 +6,7 @@ * See the COPYING-README file. */ -namespace OC\Certificate; +namespace OC\Security; use OCP\ICertificate; diff --git a/lib/private/certificate/certificatemanager.php b/lib/private/security/certificatemanager.php similarity index 99% rename from lib/private/certificate/certificatemanager.php rename to lib/private/security/certificatemanager.php index d7180f7f3f..2888fecd23 100644 --- a/lib/private/certificate/certificatemanager.php +++ b/lib/private/security/certificatemanager.php @@ -6,7 +6,7 @@ * See the COPYING-README file. */ -namespace OC\Certificate; +namespace OC\Security; use OC\Files\Filesystem; use OCP\ICertificateManager; diff --git a/lib/private/server.php b/lib/private/server.php index 311001ce40..62ef9bfb61 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -6,7 +6,7 @@ use OC\AppFramework\Http\Request; use OC\AppFramework\Db\Db; use OC\AppFramework\Utility\SimpleContainer; use OC\Cache\UserCache; -use OC\Certificate\CertificateManager; +use OC\Security\CertificateManager; use OC\DB\ConnectionWrapper; use OC\Files\Node\Root; use OC\Files\View; From 2fcedcc50382127492ca0bf071e8e1bbe5736b16 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 19 Aug 2014 16:03:09 +0200 Subject: [PATCH 12/18] fix test --- apps/files_sharing/tests/externalstorage.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/files_sharing/tests/externalstorage.php b/apps/files_sharing/tests/externalstorage.php index 1258148af5..2e93afa198 100644 --- a/apps/files_sharing/tests/externalstorage.php +++ b/apps/files_sharing/tests/externalstorage.php @@ -65,6 +65,7 @@ class Test_Files_Sharing_External_Storage extends \PHPUnit_Framework_TestCase { * @dataProvider optionsProvider */ public function testStorageMountOptions($inputUri, $baseUri) { + $certificateManager = \OC::$server->getCertificateManager(); $storage = new TestSharingExternalStorage( array( 'remote' => $inputUri, @@ -72,7 +73,8 @@ class Test_Files_Sharing_External_Storage extends \PHPUnit_Framework_TestCase { 'mountpoint' => 'remoteshare', 'token' => 'abcdef', 'password' => '', - 'manager' => null + 'manager' => null, + 'certificateManager' => $certificateManager ) ); $this->assertEquals($baseUri, $storage->getBaseUri()); From e64aa330fdd6ce6180a0ffcf3634dbcae31abfff Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 21 Aug 2014 14:09:40 +0200 Subject: [PATCH 13/18] check for blacklisted file certificate filenames --- lib/private/security/certificatemanager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/security/certificatemanager.php b/lib/private/security/certificatemanager.php index 2888fecd23..64a1d6431a 100644 --- a/lib/private/security/certificatemanager.php +++ b/lib/private/security/certificatemanager.php @@ -78,7 +78,7 @@ class CertificateManager implements ICertificateManager { * @return bool | \OCP\ICertificate */ public function addCertificate($certificate, $name) { - if (!\OC\Files\Filesystem::isValidPath($name)) { + if (!Filesystem::isValidPath($name) or Filesystem::isFileBlacklisted($name)) { return false; } $isValid = openssl_pkey_get_public($certificate); From 1361bbb1e6a47266cf3a11b2ddba77706522d9e0 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 21 Aug 2014 14:15:43 +0200 Subject: [PATCH 14/18] Cleanup routes --- settings/js/personal.js | 3 +-- settings/routes.php | 4 ++-- settings/templates/personal.php | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/settings/js/personal.js b/settings/js/personal.js index d6763cdb29..d6546717d1 100644 --- a/settings/js/personal.js +++ b/settings/js/personal.js @@ -306,7 +306,7 @@ $(document).ready(function () { $('#sslCertificate').on('click', 'td.remove > img', function () { var row = $(this).parent().parent(); - $.post(OC.filePath('settings', 'ajax', 'removeRootCertificate.php'), { + $.post(OC.generateUrl('settings/ajax/removeRootCertificate'), { cert: row.data('name') }); row.remove(); @@ -317,7 +317,6 @@ $(document).ready(function () { $('#rootcert_import').fileupload({ done: function (e, data) { - console.log(data.result); var issueDate = new Date(data.result.validFrom * 1000); var expireDate = new Date(data.result.validTill * 1000); var now = new Date(); diff --git a/settings/routes.php b/settings/routes.php index c7bab409d4..191b5febbd 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -62,9 +62,9 @@ $this->create('settings_ajax_restorekeys', '/settings/ajax/restorekeys.php') ->actionInclude('settings/ajax/restorekeys.php'); $this->create('settings_ajax_deletekeys', '/settings/ajax/deletekeys.php') ->actionInclude('settings/ajax/deletekeys.php'); -$this->create('core_cert_post', '/settings/ajax/addRootCertificate.php') +$this->create('settings_cert_post', '/settings/ajax/addRootCertificate') ->actionInclude('settings/ajax/addRootCertificate.php'); -$this->create('core_cert_remove', '/settings/ajax/removeRootCertificate.php') +$this->create('settings_cert_remove', '/settings/ajax/removeRootCertificate') ->actionInclude('settings/ajax/removeRootCertificate.php'); // apps $this->create('settings_ajax_apps_ocs', '/settings/ajax/apps/ocs.php') diff --git a/settings/templates/personal.php b/settings/templates/personal.php index 6d07b89954..8cb7be2773 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -182,7 +182,7 @@ if($_['passwordChangeSupported']) { -
+
From 4efe6f62402482608cb1b2f4c51b9b3e41603733 Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Wed, 27 Aug 2014 16:28:51 +0200 Subject: [PATCH 15/18] Add unit tests and fix rootcerts creation bug --- lib/private/security/certificate.php | 27 +++--- lib/private/security/certificatemanager.php | 37 ++++---- settings/ajax/addRootCertificate.php | 7 +- settings/js/personal.js | 2 +- settings/templates/personal.php | 9 +- tests/data/certificates/badCertificate.crt | 13 +++ .../data/certificates/expiredCertificate.crt | 13 +++ tests/data/certificates/goodCertificate.crt | 15 ++++ tests/lib/security/certificate.php | 90 +++++++++++++++++++ tests/lib/security/certificatemanager.php | 87 ++++++++++++++++++ 10 files changed, 263 insertions(+), 37 deletions(-) create mode 100644 tests/data/certificates/badCertificate.crt create mode 100644 tests/data/certificates/expiredCertificate.crt create mode 100644 tests/data/certificates/goodCertificate.crt create mode 100644 tests/lib/security/certificate.php create mode 100644 tests/lib/security/certificatemanager.php diff --git a/lib/private/security/certificate.php b/lib/private/security/certificate.php index 953111f469..63c02a124f 100644 --- a/lib/private/security/certificate.php +++ b/lib/private/security/certificate.php @@ -30,17 +30,22 @@ class Certificate implements ICertificate { /** * @param string $data base64 encoded certificate * @param string $name + * @throws \Exception If the certificate could not get parsed */ public function __construct($data, $name) { $this->name = $name; - $info = openssl_x509_parse($data); - $this->commonName = $info['subject']['CN']; - $this->organization = isset($info['subject']['O']) ? $info['subject']['O'] : null; - $this->serial = $this->formatSerial($info['serialNumber']); - $this->issueDate = new \DateTime('@' . $info['validFrom_time_t']); - $this->expireDate = new \DateTime('@' . $info['validTo_time_t']); - $this->issuerName = $info['issuer']['CN']; - $this->issuerOrganization = isset($info['issuer']['O']) ? $info['issuer']['O'] : null; + try { + $info = openssl_x509_parse($data); + $this->commonName = isset($info['subject']['CN']) ? $info['subject']['CN'] : null; + $this->organization = isset($info['subject']['O']) ? $info['subject']['O'] : null; + $this->serial = $this->formatSerial($info['serialNumber']); + $this->issueDate = new \DateTime('@' . $info['validFrom_time_t']); + $this->expireDate = new \DateTime('@' . $info['validTo_time_t']); + $this->issuerName = isset($info['issuer']['CN']) ? $info['issuer']['CN'] : null; + $this->issuerOrganization = isset($info['issuer']['O']) ? $info['issuer']['O'] : null; + } catch (\Exception $e) { + throw new \Exception('Certificate could not get parsed.'); + } } /** @@ -62,7 +67,7 @@ class Certificate implements ICertificate { } /** - * @return string + * @return string|null */ public function getCommonName() { return $this->commonName; @@ -105,14 +110,14 @@ class Certificate implements ICertificate { } /** - * @return string + * @return string|null */ public function getIssuerName() { return $this->issuerName; } /** - * @return string + * @return string|null */ public function getIssuerOrganization() { return $this->issuerOrganization; diff --git a/lib/private/security/certificatemanager.php b/lib/private/security/certificatemanager.php index 64a1d6431a..cae9730eb2 100644 --- a/lib/private/security/certificatemanager.php +++ b/lib/private/security/certificatemanager.php @@ -44,7 +44,9 @@ class CertificateManager implements ICertificateManager { } while (false !== ($file = readdir($handle))) { if ($file != '.' && $file != '..') { - $result[] = new Certificate(file_get_contents($path . $file), $file); + try { + $result[] = new Certificate(file_get_contents($path . $file), $file); + } catch(\Exception $e) {} } } return $result; @@ -59,7 +61,7 @@ class CertificateManager implements ICertificateManager { $fh_certs = fopen($path . '/rootcerts.crt', 'w'); foreach ($certs as $cert) { - $file = $path . '/uploads/' . $cert; + $file = $path . '/uploads/' . $cert->getName(); $data = file_get_contents($file); if (strpos($data, 'BEGIN CERTIFICATE')) { fwrite($fh_certs, $data); @@ -75,35 +77,32 @@ class CertificateManager implements ICertificateManager { * * @param string $certificate the certificate data * @param string $name the filename for the certificate - * @return bool | \OCP\ICertificate + * @return \OCP\ICertificate|void|bool + * @throws \Exception If the certificate could not get added */ public function addCertificate($certificate, $name) { if (!Filesystem::isValidPath($name) or Filesystem::isFileBlacklisted($name)) { return false; } - $isValid = openssl_pkey_get_public($certificate); - if (!$isValid) { - $data = chunk_split(base64_encode($certificate), 64, "\n"); - $data = "-----BEGIN CERTIFICATE-----\n" . $data . "-----END CERTIFICATE-----\n"; - $isValid = openssl_pkey_get_public($data); + $dir = $this->user->getHome() . '/files_external/uploads/'; + if (!file_exists($dir)) { + //path might not exist (e.g. non-standard OC_User::getHome() value) + //in this case create full path using 3rd (recursive=true) parameter. + //note that we use "normal" php filesystem functions here since the certs need to be local + mkdir($dir, 0700, true); } - if ($isValid) { - $dir = $this->user->getHome() . '/files_external/uploads/'; - if (!file_exists($dir)) { - //path might not exist (e.g. non-standard OC_User::getHome() value) - //in this case create full path using 3rd (recursive=true) parameter. - //note that we use "normal" php filesystem functions here since the certs need to be local - mkdir($dir, 0700, true); - } + try { $file = $dir . $name; + $certificateObject = new Certificate($certificate, $name); file_put_contents($file, $certificate); $this->createCertificateBundle(); - return new Certificate($certificate, $name); - } else { - return false; + return $certificateObject; + } catch (\Exception $e) { + throw $e; } + } /** diff --git a/settings/ajax/addRootCertificate.php b/settings/ajax/addRootCertificate.php index 87b1460ef1..378ef39c1e 100644 --- a/settings/ajax/addRootCertificate.php +++ b/settings/ajax/addRootCertificate.php @@ -1,4 +1,5 @@ getCertificateManager(); -$cert = $certificateManager->addCertificate($data, $filename); -if ($cert) { +try { + $cert = $certificateManager->addCertificate($data, $filename); OCP\JSON::success(array( 'name' => $cert->getName(), 'commonName' => $cert->getCommonName(), @@ -26,6 +27,6 @@ if ($cert) { 'issuer' => $cert->getIssuerName(), 'issuerOrganization' => $cert->getIssuerOrganization() )); -} else { +} catch(\Exception $e) { OCP\JSON::error(array('error' => 'Couldn\'t import SSL root certificate, allowed formats: PEM and DER')); } diff --git a/settings/js/personal.js b/settings/js/personal.js index d6546717d1..11e9593d74 100644 --- a/settings/js/personal.js +++ b/settings/js/personal.js @@ -325,7 +325,7 @@ $(document).ready(function () { var row = $(''); row.addClass(isExpired? 'expired': 'valid'); row.append($('').attr('title', data.result.organization).text(data.result.commonName)); - row.append($('').attr('title', t('core,', 'Valid from {date}', {date: data.result.validFromString})) + row.append($('').attr('title', t('core,', 'Valid until {date}', {date: data.result.validFromString})) .text(data.result.validTillString)); row.append($('').attr('title', data.result.issuerOrganization).text(data.result.issuer)); row.append($('').addClass('remove').append( diff --git a/settings/templates/personal.php b/settings/templates/personal.php index 8cb7be2773..871a0ec9e3 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -2,7 +2,10 @@ * Copyright (c) 2011, Robin Appelman * This file is licensed under the Affero General Public License version 3 or later. * See the COPYING-README file. - */?> + */ + +/** @var $_ array */ +?>

t('Get the apps to sync your files'));?>

@@ -160,12 +163,12 @@ if($_['passwordChangeSupported']) { - + getCommonName()) ?> - + l('date', $rootCert->getExpireDate()) ?> diff --git a/tests/data/certificates/badCertificate.crt b/tests/data/certificates/badCertificate.crt new file mode 100644 index 0000000000..dcb1895fba --- /dev/null +++ b/tests/data/certificates/badCertificate.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICATCCAWoCCQDNdmb4pJrUeDANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB +VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMB4XDTE0MDgyNzA4NDg1MVoXDTE1MDgyNzA4NDg1MVowRTELMAkG +A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0 +IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvrMe +x5D45HVMV2U4kqTU0mzHAihHT6r+OtO6g7S9yIlJZGGVcEet6An78Ow7aYM141eI +Jfbvqql7OIblHXSw7mvkw4bOQ1ee5lmJYOYCgaMNJ6mBLJfpK9xwidb0ZvhWOA8P +DLIiBKA3T5ChXCzilD5GF2+H/BXBE9lL9tuDjM0CAwEAATANBgkqhkiG9w0BAQUF +AAOBgQCJwfJe7j+aNkopw+P8uxobfOnMWU9XC4Pu+39TVLeakeSqu2Y8vJSHmkjF +WK3VXAJr33Eul5VP/3SWGwuRPd9X4i4iLh1gJfYvi9MJf1lQNYncGCM+xtdrNu2O +u0yexkOBRrapDYjcv58BiOaFgvFLquKvtVj9HlcYRfwfM77uKQ== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/tests/data/certificates/expiredCertificate.crt b/tests/data/certificates/expiredCertificate.crt new file mode 100644 index 0000000000..5e7e5df2cb --- /dev/null +++ b/tests/data/certificates/expiredCertificate.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICATCCAWoCCQCjCIB6tCZ2sDANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB +VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMB4XDTE0MDgyNzA5MTI0M1oXDTE0MDgyODA5MTI0M1owRTELMAkG +A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0 +IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvrMe +x5D45HVMV2U4kqTU0mzHAihHT6r+OtO6g7S9yIlJZGGVcEet6An78Ow7aYM141eI +Jfbvqql7OIblHXSw7mvkw4bOQ1ee5lmJYOYCgaMNJ6mBLJfpK9xwidb0ZvhWOA8P +DLIiBKA3T5ChXCzilD5GF2+H/BXBE9lL9tuDjM0CAwEAATANBgkqhkiG9w0BAQUF +AAOBgQBuNClmOj3wudlX86nygcZgQT2+ZS8f1iJgM9lbrrkenT6tgcT1/YjcrN9C +BZR29Wz7htflpqverLUGZXh72K+gYercyR16Zu7zjt/NWuZldZmzJ3bUGq2HSoCX +2sDykAEuaDxUlzdJrztlOH4vPlRaGbxUogpC2hB1BQfxA90CIA== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/tests/data/certificates/goodCertificate.crt b/tests/data/certificates/goodCertificate.crt new file mode 100644 index 0000000000..4a5d7bd32f --- /dev/null +++ b/tests/data/certificates/goodCertificate.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICazCCAdQCCQCySF7HjQD78DANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJD +SDEPMA0GA1UECBMGWnVyaWNoMQ8wDQYDVQQHEwZadXJpY2gxFjAUBgNVBAoTDW93 +bkNsb3VkIEluYy4xETAPBgNVBAsTCFNlY3VyaXR5MR4wHAYDVQQDExVzZWN1cml0 +eS5vd25jbG91ZC5jb20wHhcNMTQwODI3MDg0NTUyWhcNMTUwODI3MDg0NTUyWjB6 +MQswCQYDVQQGEwJDSDEPMA0GA1UECBMGWnVyaWNoMQ8wDQYDVQQHEwZadXJpY2gx +FjAUBgNVBAoTDW93bkNsb3VkIEluYy4xETAPBgNVBAsTCFNlY3VyaXR5MR4wHAYD +VQQDExVzZWN1cml0eS5vd25jbG91ZC5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0A +MIGJAoGBAL55lB4RvU0pTyh7YsLCxPBq43xxkRZBxfZENoflCIUsBo7/mXNz2zVO +476oQ4L47heUOX3j8kemOgPmWEqA34JB8rusijCy5WqFBLnm4HsRLa66i+Jgd+Yl +QhcKvhGas1K/CVTG4oSLoAmA2coZUL94uxnRtd8aluflHMNGApIlAgMBAAEwDQYJ +KoZIhvcNAQEFBQADgYEADo08zWdOtIvCKFDnLbzRwIjSYTlAtQtQaULv7KQe3qIn +iaFAi6fAynHfdC8/2tvmSeniw0OZBkrfVGIVtUbwCSrljNSUY/lWrUR0pE61lb4r +DpX0JZjlk48XEaErRVDfu3wq6n/2nYg6HnaLOPwt8OSYYrxzvXlFPrKBH3q6R+M= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/tests/lib/security/certificate.php b/tests/lib/security/certificate.php new file mode 100644 index 0000000000..694d1f2701 --- /dev/null +++ b/tests/lib/security/certificate.php @@ -0,0 +1,90 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. +*/ + +use \OC\Security\Certificate; + +class CertificateTest extends \PHPUnit_Framework_TestCase { + + /** @var Certificate That contains a valid certificate */ + protected $goodCertificate; + /** @var Certificate That contains an invalid certificate */ + protected $invalidCertificate; + /** @var Certificate That contains an expired certificate */ + protected $expiredCertificate; + + function setUp() { + $goodCertificate = file_get_contents(__DIR__.'/../../data/certificates/goodCertificate.crt'); + $this->goodCertificate = new Certificate($goodCertificate, 'GoodCertificate'); + $badCertificate = file_get_contents(__DIR__.'/../../data/certificates/badCertificate.crt'); + $this->invalidCertificate = new Certificate($badCertificate, 'BadCertificate'); + $expiredCertificate = file_get_contents(__DIR__.'/../../data/certificates/expiredCertificate.crt'); + $this->expiredCertificate = new Certificate($expiredCertificate, 'ExpiredCertificate'); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Certificate could not get parsed. + */ + function testBogusData() { + new Certificate('foo', 'bar'); + } + + function testGetName() { + $this->assertSame('GoodCertificate', $this->goodCertificate->getName()); + $this->assertSame('BadCertificate', $this->invalidCertificate->getName()); + } + + function testGetCommonName() { + $this->assertSame('security.owncloud.com', $this->goodCertificate->getCommonName()); + $this->assertSame(null, $this->invalidCertificate->getCommonName()); + } + + function testGetOrganization() { + $this->assertSame('ownCloud Inc.', $this->goodCertificate->getOrganization()); + $this->assertSame('Internet Widgits Pty Ltd', $this->invalidCertificate->getOrganization()); + } + + function testGetSerial() { + $this->assertSame('7F:FF:FF:FF:FF:FF:FF:FF', $this->goodCertificate->getSerial()); + $this->assertSame('7F:FF:FF:FF:FF:FF:FF:FF', $this->invalidCertificate->getSerial()); + } + + function testGetIssueDate() { + $this->assertEquals(new DateTime('2014-08-27 08:45:52'), $this->goodCertificate->getIssueDate()); + $this->assertEquals(new DateTime('2014-08-27 08:48:51'), $this->invalidCertificate->getIssueDate()); + } + + function testGetExpireDate() { + $this->assertEquals(new DateTime('2015-08-27 08:45:52'), $this->goodCertificate->getExpireDate()); + $this->assertEquals(new DateTime('2015-08-27 08:48:51'), $this->invalidCertificate->getExpireDate()); + $this->assertEquals(new DateTime('2014-08-28 09:12:43'), $this->expiredCertificate->getExpireDate()); + } + + /** + * Obviously the following test case might fail after 2015-08-27, just create a new certificate with longer validity then + */ + function testIsExpired() { + $this->assertSame(false, $this->goodCertificate->isExpired()); + $this->assertSame(false, $this->invalidCertificate->isExpired()); + + // TODO: Change to false after tomorrow + $this->assertSame(false, $this->expiredCertificate->isExpired()); + } + + function testGetIssuerName() { + $this->assertSame('security.owncloud.com', $this->goodCertificate->getIssuerName()); + $this->assertSame(null, $this->invalidCertificate->getIssuerName()); + $this->assertSame(null, $this->expiredCertificate->getIssuerName()); + } + + function testGetIssuerOrganization() { + $this->assertSame('ownCloud Inc.', $this->goodCertificate->getIssuerOrganization()); + $this->assertSame('Internet Widgits Pty Ltd', $this->invalidCertificate->getIssuerOrganization()); + $this->assertSame('Internet Widgits Pty Ltd', $this->expiredCertificate->getIssuerOrganization()); + } +} \ No newline at end of file diff --git a/tests/lib/security/certificatemanager.php b/tests/lib/security/certificatemanager.php new file mode 100644 index 0000000000..5baf9e16e8 --- /dev/null +++ b/tests/lib/security/certificatemanager.php @@ -0,0 +1,87 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +use \OC\Security\CertificateManager; + +class CertificateManagerTest extends \PHPUnit_Framework_TestCase { + + /** @var CertificateManager */ + private $certificateManager; + /** @var String */ + private $username; + /** @var \OC\User\User */ + private $user; + + function setUp() { + $this->username = OC_Util::generateRandomBytes(20); + OC_User::createUser($this->username, OC_Util::generateRandomBytes(20)); + + \OC_Util::tearDownFS(); + \OC_User::setUserId(''); + \OC\Files\Filesystem::tearDown(); + \OC_Util::setupFS($this->username); + + $this->user = \OC::$server->getUserManager()->get($this->username); + + $this->certificateManager = new CertificateManager($this->user); + } + + function tearDown() { + \OC_User::deleteUser($this->username); + } + + protected function assertEqualsArrays($expected, $actual) { + sort($expected); + sort($actual); + + $this->assertEquals($expected, $actual); + } + + function testListCertificates() { + // Test empty certificate bundle + $this->assertSame(array(), $this->certificateManager->listCertificates()); + + // Add some certificates + $this->certificateManager->addCertificate(file_get_contents(__DIR__.'/../../data/certificates/goodCertificate.crt'), 'GoodCertificate'); + $certificateStore = array(); + $certificateStore[] = new \OC\Security\Certificate(file_get_contents(__DIR__.'/../../data/certificates/goodCertificate.crt'), 'GoodCertificate'); + $this->assertEqualsArrays($certificateStore, $this->certificateManager->listCertificates()); + + // Add another certificates + $this->certificateManager->addCertificate(file_get_contents(__DIR__.'/../../data/certificates/expiredCertificate.crt'), 'ExpiredCertificate'); + $certificateStore[] = new \OC\Security\Certificate(file_get_contents(__DIR__.'/../../data/certificates/expiredCertificate.crt'), 'ExpiredCertificate'); + $this->assertEqualsArrays($certificateStore, $this->certificateManager->listCertificates()); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Certificate could not get parsed. + */ + function testAddInvalidCertificate() { + $this->certificateManager->addCertificate('InvalidCertificate', 'invalidCertificate'); + } + + function testAddDangerousFile() { + $this->assertFalse($this->certificateManager->addCertificate(file_get_contents(__DIR__.'/../../data/certificates/expiredCertificate.crt'), '.htaccess')); + $this->assertFalse($this->certificateManager->addCertificate(file_get_contents(__DIR__.'/../../data/certificates/expiredCertificate.crt'), '../../foo.txt')); + } + + function testRemoveDangerousFile() { + $this->assertFalse($this->certificateManager->removeCertificate('../../foo.txt')); + } + + function testRemoveExistingFile() { + $this->certificateManager->addCertificate(file_get_contents(__DIR__.'/../../data/certificates/goodCertificate.crt'), 'GoodCertificate'); + $this->assertTrue($this->certificateManager->removeCertificate('GoodCertificate')); + } + + function testGetCertificateBundle() { + $this->assertSame($this->user->getHome().'/files_external/rootcerts.crt', $this->certificateManager->getCertificateBundle()); + } + +} \ No newline at end of file From 4bc9980f4be1ed9834768b613af6a6e43dc83556 Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Wed, 27 Aug 2014 16:29:42 +0200 Subject: [PATCH 16/18] Add test for expired certificate Will only work after tomorrow --- tests/lib/security/certificate.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/lib/security/certificate.php b/tests/lib/security/certificate.php index 694d1f2701..9ae68f2cf1 100644 --- a/tests/lib/security/certificate.php +++ b/tests/lib/security/certificate.php @@ -71,9 +71,7 @@ class CertificateTest extends \PHPUnit_Framework_TestCase { function testIsExpired() { $this->assertSame(false, $this->goodCertificate->isExpired()); $this->assertSame(false, $this->invalidCertificate->isExpired()); - - // TODO: Change to false after tomorrow - $this->assertSame(false, $this->expiredCertificate->isExpired()); + $this->assertSame(true, $this->expiredCertificate->isExpired()); } function testGetIssuerName() { From bfa0c4b78a977726971f7b48e8f921b8adb56e8b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 31 Aug 2014 11:06:18 +0200 Subject: [PATCH 17/18] Explicitly set the timezones --- lib/private/security/certificate.php | 5 +++-- tests/lib/security/certificate.php | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/private/security/certificate.php b/lib/private/security/certificate.php index 63c02a124f..778524507e 100644 --- a/lib/private/security/certificate.php +++ b/lib/private/security/certificate.php @@ -35,12 +35,13 @@ class Certificate implements ICertificate { public function __construct($data, $name) { $this->name = $name; try { + $gmt = new \DateTimeZone('GMT'); $info = openssl_x509_parse($data); $this->commonName = isset($info['subject']['CN']) ? $info['subject']['CN'] : null; $this->organization = isset($info['subject']['O']) ? $info['subject']['O'] : null; $this->serial = $this->formatSerial($info['serialNumber']); - $this->issueDate = new \DateTime('@' . $info['validFrom_time_t']); - $this->expireDate = new \DateTime('@' . $info['validTo_time_t']); + $this->issueDate = new \DateTime('@' . $info['validFrom_time_t'], $gmt); + $this->expireDate = new \DateTime('@' . $info['validTo_time_t'], $gmt); $this->issuerName = isset($info['issuer']['CN']) ? $info['issuer']['CN'] : null; $this->issuerOrganization = isset($info['issuer']['O']) ? $info['issuer']['O'] : null; } catch (\Exception $e) { diff --git a/tests/lib/security/certificate.php b/tests/lib/security/certificate.php index 9ae68f2cf1..41c8a74b83 100644 --- a/tests/lib/security/certificate.php +++ b/tests/lib/security/certificate.php @@ -55,14 +55,14 @@ class CertificateTest extends \PHPUnit_Framework_TestCase { } function testGetIssueDate() { - $this->assertEquals(new DateTime('2014-08-27 08:45:52'), $this->goodCertificate->getIssueDate()); - $this->assertEquals(new DateTime('2014-08-27 08:48:51'), $this->invalidCertificate->getIssueDate()); + $this->assertEquals((new DateTime('2014-08-27 08:45:52 GMT'))->getTimestamp(), $this->goodCertificate->getIssueDate()->getTimestamp()); + $this->assertEquals((new DateTime('2014-08-27 08:48:51 GMT'))->getTimestamp(), $this->invalidCertificate->getIssueDate()->getTimestamp()); } function testGetExpireDate() { - $this->assertEquals(new DateTime('2015-08-27 08:45:52'), $this->goodCertificate->getExpireDate()); - $this->assertEquals(new DateTime('2015-08-27 08:48:51'), $this->invalidCertificate->getExpireDate()); - $this->assertEquals(new DateTime('2014-08-28 09:12:43'), $this->expiredCertificate->getExpireDate()); + $this->assertEquals((new DateTime('2015-08-27 08:45:52 GMT'))->getTimestamp(), $this->goodCertificate->getExpireDate()->getTimestamp()); + $this->assertEquals((new DateTime('2015-08-27 08:48:51 GMT'))->getTimestamp(), $this->invalidCertificate->getExpireDate()->getTimestamp()); + $this->assertEquals((new DateTime('2014-08-28 09:12:43 GMT'))->getTimestamp(), $this->expiredCertificate->getExpireDate()->getTimestamp()); } /** @@ -85,4 +85,4 @@ class CertificateTest extends \PHPUnit_Framework_TestCase { $this->assertSame('Internet Widgits Pty Ltd', $this->invalidCertificate->getIssuerOrganization()); $this->assertSame('Internet Widgits Pty Ltd', $this->expiredCertificate->getIssuerOrganization()); } -} \ No newline at end of file +} From 3a85767182e04ac013f59d82cc3a8c4d08bab151 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 31 Aug 2014 13:16:28 +0200 Subject: [PATCH 18/18] 5.3 syntax... --- tests/lib/security/certificate.php | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/tests/lib/security/certificate.php b/tests/lib/security/certificate.php index 41c8a74b83..db33dd00d9 100644 --- a/tests/lib/security/certificate.php +++ b/tests/lib/security/certificate.php @@ -4,7 +4,7 @@ * This file is licensed under the Affero General Public License version 3 or * later. * See the COPYING-README file. -*/ + */ use \OC\Security\Certificate; @@ -18,11 +18,11 @@ class CertificateTest extends \PHPUnit_Framework_TestCase { protected $expiredCertificate; function setUp() { - $goodCertificate = file_get_contents(__DIR__.'/../../data/certificates/goodCertificate.crt'); + $goodCertificate = file_get_contents(__DIR__ . '/../../data/certificates/goodCertificate.crt'); $this->goodCertificate = new Certificate($goodCertificate, 'GoodCertificate'); - $badCertificate = file_get_contents(__DIR__.'/../../data/certificates/badCertificate.crt'); + $badCertificate = file_get_contents(__DIR__ . '/../../data/certificates/badCertificate.crt'); $this->invalidCertificate = new Certificate($badCertificate, 'BadCertificate'); - $expiredCertificate = file_get_contents(__DIR__.'/../../data/certificates/expiredCertificate.crt'); + $expiredCertificate = file_get_contents(__DIR__ . '/../../data/certificates/expiredCertificate.crt'); $this->expiredCertificate = new Certificate($expiredCertificate, 'ExpiredCertificate'); } @@ -55,14 +55,19 @@ class CertificateTest extends \PHPUnit_Framework_TestCase { } function testGetIssueDate() { - $this->assertEquals((new DateTime('2014-08-27 08:45:52 GMT'))->getTimestamp(), $this->goodCertificate->getIssueDate()->getTimestamp()); - $this->assertEquals((new DateTime('2014-08-27 08:48:51 GMT'))->getTimestamp(), $this->invalidCertificate->getIssueDate()->getTimestamp()); + $expected = new DateTime('2014-08-27 08:45:52 GMT'); + $this->assertEquals($expected->getTimestamp(), $this->goodCertificate->getIssueDate()->getTimestamp()); + $expected = new DateTime('2014-08-27 08:48:51 GMT'); + $this->assertEquals($expected->getTimestamp(), $this->invalidCertificate->getIssueDate()->getTimestamp()); } function testGetExpireDate() { - $this->assertEquals((new DateTime('2015-08-27 08:45:52 GMT'))->getTimestamp(), $this->goodCertificate->getExpireDate()->getTimestamp()); - $this->assertEquals((new DateTime('2015-08-27 08:48:51 GMT'))->getTimestamp(), $this->invalidCertificate->getExpireDate()->getTimestamp()); - $this->assertEquals((new DateTime('2014-08-28 09:12:43 GMT'))->getTimestamp(), $this->expiredCertificate->getExpireDate()->getTimestamp()); + $expected = new DateTime('2015-08-27 08:45:52 GMT'); + $this->assertEquals($expected->getTimestamp(), $this->goodCertificate->getExpireDate()->getTimestamp()); + $expected = new DateTime('2015-08-27 08:48:51 GMT'); + $this->assertEquals($expected->getTimestamp(), $this->invalidCertificate->getExpireDate()->getTimestamp()); + $expected = new DateTime('2014-08-28 09:12:43 GMT'); + $this->assertEquals($expected->getTimestamp(), $this->expiredCertificate->getExpireDate()->getTimestamp()); } /**