diff --git a/apps/files_external/ajax/addRootCertificate.php b/apps/files_external/ajax/addRootCertificate.php deleted file mode 100644 index fcd3a617ad..0000000000 --- a/apps/files_external/ajax/addRootCertificate.php +++ /dev/null @@ -1,45 +0,0 @@ -file_exists('')) { - $view->mkdir(''); -} - -$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 { - 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/ajax/removeRootCertificate.php b/apps/files_external/ajax/removeRootCertificate.php deleted file mode 100644 index 664b3937e9..0000000000 --- a/apps/files_external/ajax/removeRootCertificate.php +++ /dev/null @@ -1,13 +0,0 @@ -file_exists($file) ) { - $view->unlink($file); - OC_Mount_Config::createCertificateBundle(); -} 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..a279163ff7 100755 --- a/apps/files_external/personal.php +++ b/apps/files_external/personal.php @@ -27,7 +27,6 @@ $backends = OC_Mount_Config::getPersonalBackends(); $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('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 @@
- - - - 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..92d8f92b38 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,6 +196,10 @@ class Storage extends DAV implements ISharedStorage { http_build_query(array('password' => $password))); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + 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); 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()); 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/private/security/certificate.php b/lib/private/security/certificate.php new file mode 100644 index 0000000000..778524507e --- /dev/null +++ b/lib/private/security/certificate.php @@ -0,0 +1,126 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Security; + +use OCP\ICertificate; + +class Certificate implements ICertificate { + protected $name; + + protected $commonName; + + protected $organization; + + protected $serial; + + protected $issueDate; + + protected $expireDate; + + protected $issuerName; + + protected $issuerOrganization; + + /** + * @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; + 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'], $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) { + throw new \Exception('Certificate could not get parsed.'); + } + } + + /** + * 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|null + */ + public function getCommonName() { + return $this->commonName; + } + + /** + * @return string + */ + public function getOrganization() { + return $this->organization; + } + + /** + * @return string + */ + public function getSerial() { + return $this->serial; + } + + /** + * @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 or $now > $this->expireDate; + } + + /** + * @return string|null + */ + public function getIssuerName() { + return $this->issuerName; + } + + /** + * @return string|null + */ + public function getIssuerOrganization() { + return $this->issuerOrganization; + } +} diff --git a/lib/private/security/certificatemanager.php b/lib/private/security/certificatemanager.php new file mode 100644 index 0000000000..cae9730eb2 --- /dev/null +++ b/lib/private/security/certificatemanager.php @@ -0,0 +1,134 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Security; + +use OC\Files\Filesystem; +use OCP\ICertificateManager; + +/** + * Manage trusted certificates for users + */ +class CertificateManager implements ICertificateManager { + /** + * @var \OCP\IUser + */ + protected $user; + + /** + * @param \OCP\IUser $user + */ + public function __construct($user) { + $this->user = $user; + } + + /** + * Returns all certificates trusted by the user + * + * @return \OCP\ICertificate[] + */ + public function listCertificates() { + $path = $this->user->getHome() . '/files_external/uploads/'; + if (!is_dir($path)) { + return array(); + } + $result = array(); + $handle = opendir($path); + if (!is_resource($handle)) { + return array(); + } + while (false !== ($file = readdir($handle))) { + if ($file != '.' && $file != '..') { + try { + $result[] = new Certificate(file_get_contents($path . $file), $file); + } catch(\Exception $e) {} + } + } + 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->getName(); + $data = file_get_contents($file); + if (strpos($data, 'BEGIN CERTIFICATE')) { + fwrite($fh_certs, $data); + fwrite($fh_certs, "\r\n"); + } + } + + fclose($fh_certs); + } + + /** + * Save the certificate and re-generate the certificate bundle + * + * @param string $certificate the certificate data + * @param string $name the filename for the certificate + * @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; + } + + $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 $certificateObject; + } catch (\Exception $e) { + throw $e; + } + + } + + /** + * Remove the certificate and re-generate the certificate bundle + * + * @param string $name + * @return bool + */ + public function removeCertificate($name) { + if (!Filesystem::isValidPath($name)) { + return false; + } + $path = $this->user->getHome() . '/files_external/uploads/'; + if (file_exists($path . $name)) { + unlink($path . $name); + $this->createCertificateBundle(); + } + return true; + } + + /** + * 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 28c4fe6085..5d40f1327f 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\Security\CertificateManager; use OC\DB\ConnectionWrapper; use OC\Files\Node\Root; use OC\Files\View; @@ -474,4 +475,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/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(); +} diff --git a/lib/public/icertificatemanager.php b/lib/public/icertificatemanager.php new file mode 100644 index 0000000000..24b8d12363 --- /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 \OCP\ICertificate[] + */ + public function listCertificates(); + + /** + * @param string $certificate the certificate data + * @param string $name the filename for the certificate + * @return bool | \OCP\ICertificate + */ + 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 64f5f350b1..60b0b497c5 100644 --- a/lib/public/iservercontainer.php +++ b/lib/public/iservercontainer.php @@ -228,4 +228,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); } diff --git a/settings/ajax/addRootCertificate.php b/settings/ajax/addRootCertificate.php new file mode 100644 index 0000000000..378ef39c1e --- /dev/null +++ b/settings/ajax/addRootCertificate.php @@ -0,0 +1,32 @@ + 'No certificate uploaded')); + exit; +} + +$data = file_get_contents($_FILES['rootcert_import']['tmp_name']); +$filename = basename($_FILES['rootcert_import']['name']); + +$certificateManager = \OC::$server->getCertificateManager(); + +try { + $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() + )); +} catch(\Exception $e) { + OCP\JSON::error(array('error' => 'Couldn\'t import SSL root certificate, allowed formats: PEM and DER')); +} diff --git a/settings/ajax/removeRootCertificate.php b/settings/ajax/removeRootCertificate.php new file mode 100644 index 0000000000..a3de035269 --- /dev/null +++ b/settings/ajax/removeRootCertificate.php @@ -0,0 +1,7 @@ +getCertificateManager(); +$certificateManager->removeCertificate($name); 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..11e9593d74 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("t('Common Name')); ?> | +t('Valid until')); ?> | +t('Issued By')); ?> | ++ + + + |
---|---|---|---|
+ getCommonName()) ?> + | ++ l('date', $rootCert->getExpireDate()) ?> + | ++ getIssuerName()) ?> + | +class="remove"
+ style="visibility:hidden;"
+ > |
+