DaPortal

<?php //$Id$
//Copyright (c) 2015-2016 Pierre Pronchery <khorben@defora.org>
//This file is part of DeforaOS Web DaPortal
//
//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, version 3 of the License.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program. If not, see <http://www.gnu.org/licenses/>.
//CAPKIContent
class CAPKIContent extends PKIContent
{
//public
//methods
//essential
//CAPKIContent::CAPKIContent
public function __construct(Engine $engine, Module $module,
$properties = FALSE)
{
parent::__construct($engine, $module, $properties);
//translations
static::$text_content = _('Certificate Authority');
$this->text_content_by = _('Certificate Authority from');
$this->text_content_list_title = _('Certificate Authorities');
$this->text_more_content = _('More Certificate Authorities...');
$this->text_submit = _('New Certificate Authority...');
$this->text_submit_content = _('New Certificate Authority');
}
//accessors
//CAPKIContent::canSubmit
public function canSubmit(Engine $engine, Request $request = NULL,
&$error = FALSE)
{
if(parent::canSubmit($engine, $request, $error) === FALSE)
return FALSE;
if($request !== NULL)
{
$title = $request->get('title');
//check for duplicates
if(($rootca = $this->getRootCA($engine)) === FALSE)
{
$error = _('Internal error');
return FALSE;
}
if(file_exists($rootca))
{
$error = _('Duplicate CA');
return FALSE;
}
}
return TRUE;
}
//CAPKIContent::getRootCA
protected function getRootCA(Engine $engine, $parent = FALSE)
{
if(($root = $this->getRoot($engine)) === FALSE)
return FALSE;
return $root.'/'.$this->getTitle();
}
//useful
//CAPKIContent::createRoot
protected function createRoot(Engine $engine)
{
if(($root = $this->getRootCA($engine)) === FALSE)
return FALSE;
//create the CA directory
if(is_dir($root) || mkdir($root, 0700, TRUE) !== TRUE)
return FALSE;
//create sub-directories as required
$dirs = array('certs', 'crl', 'newcerts', 'newreqs', 'private');
foreach($dirs as $d)
{
$directory = $root.'/'.$d;
if($this->_rootDirectory($engine, $directory) === FALSE)
return FALSE;
}
return TRUE;
}
protected function _rootDirectory(Engine $engine, $directory)
{
if(is_dir($directory) || is_readable($directory))
return FALSE;
if(mkdir($directory, 0700, TRUE) === FALSE)
return $engine->log(LOG_ERR, $directory.': Could not'
.' create directory');
return TRUE;
}
//CAPKIContent::displayToolbar
public function displayToolbar(Engine $engine, Request $request = NULL)
{
//XXX copied from Content
$credentials = $engine->getCredentials();
$action = ($request !== NULL) ? $request->getAction() : FALSE;
$toolbar = new PageElement('toolbar');
if($credentials->isAdmin())
{
$r = $this->getModule()->getRequest('admin');
$toolbar->append('button', array('request' => $r,
'stock' => 'admin',
'text' => _('Administration')));
}
if($action != 'submit' && $this->getModule()->canSubmit($engine,
NULL, $this))
{
$types = array('ca' => $this->text_submit_content,
'caserver' => _('New CA server'),
'caclient' => _('New CA client'));
foreach($types as $type => $text)
{
$r = $this->getRequest('submit',
array('type' => $type));
$toolbar->append('button', array(
'request' => $r,
'stock' => $this->stock_submit,
'text' => $text));
}
}
if($this->getID() !== FALSE)
{
if($action != 'publish' && !$this->isPublic()
&& $this->canPublish($engine, NULL,
$this))
{
$r = $this->getRequest('publish');
$toolbar->append('button', array(
'request' => $r,
'stock' => 'publish',
'text' => $this->text_publish));
}
if($action != 'update' && $this->canUpdate($engine,
NULL, $this))
{
$r = $this->getRequest('update');
$toolbar->append('button', array(
'request' => $r,
'stock' => 'update',
'text' => $this->text_update));
}
}
return $toolbar;
}
//CAPKIContent::renew
public function renew(Engine $engine, $content = FALSE,
&$error = FALSE)
{
$root = $this->getRootCA($engine);
if($content === FALSE)
return parent::renew($engine, FALSE, $error);
if($root === FALSE)
{
$error = _('Internal error');
return FALSE;
}
if($this->revoke($engine, $content, $error) === FALSE)
return FALSE;
return $this->sign($engine, $content, $error);
}
//CAPKIContent::revoke
public function revoke(Engine $engine, $content = FALSE,
&$error = FALSE)
{
$root = $this->getRootCA($engine);
if($content === FALSE)
return parent::revoke($engine, FALSE, $error);
if($root === FALSE)
{
$error = _('Internal error');
return FALSE;
}
$opensslcnf = $root.'/openssl.cnf';
switch(get_class($content))
{
case 'CAPKIContent':
if(($root = $content->getRootCA($engine))
=== FALSE)
{
$error = _('Internal error');
return FALSE;
}
$extensions = 'v3_ca';
$out = $root.'/cacert.pem';
break;
case 'CAClientPKIContent':
$extensions = 'usr_cert';
$out = $root.'/newcerts/'.$content->getTitle().'.pem';
break;
case 'CAServerPKIContent':
$extensions = 'srv_cert';
$out = $root.'/newcerts/'.$content->getTitle().'.pem';
break;
default:
$error = _('Invalid class to sign');
return FALSE;
}
if(!file_exists($out))
{
$error = _('Could not revoke the certificate');
return $engine->log(LOG_ERR,
'Could not revoke the certificate');
}
$cmd = 'openssl ca -batch'
.' -config '.escapeshellarg($opensslcnf)
.' -revoke '.escapeshellarg($out);
$res = -1;
$engine->log(LOG_DEBUG, 'Executing: '.$cmd);
$stdout = defined('STDOUT')
? STDOUT : fopen('php://stdout', 'w');
$fds = array(1 => $stdout, 2 => $stdout);
if(($fp = proc_open($cmd, $fds, $pipes)) === FALSE
|| ($res = proc_close($fp)) != 0)
{
$error = _('Could not revoke the certificate');
return FALSE;
}
$content->set('revoked', TRUE);
$content->save($engine);
return TRUE;
}
//CAPKIContent::save
public function save(Engine $engine, Request $request = NULL,
&$error = FALSE)
{
return parent::save($engine, $request, $error);
}
protected function _saveInsert(Engine $engine, Request $request = NULL,
&$error)
{
$parent = ($request->getID() !== FALSE)
? static::load($engine, $this->getModule(),
$request->getID(), $request->getTitle())
: FALSE;
$database = $engine->getDatabase();
$query = static::$ca_query_insert;
//configuration
if(($root = $this->getRootCA($engine)) === FALSE)
{
$error = _('Internal error');
return FALSE;
}
//database transaction
$error = _('Could not insert the CA');
if(parent::_saveInsert($engine, $request, $error) === FALSE)
return FALSE;
$args = array('ca_id' => $this->getID(),
'parent' => ($parent !== FALSE) ? $parent->getID()
: NULL,
'country' => $request->get('country') ?: '',
'state' => $request->get('state') ?: '',
'locality' => $request->get('locality') ?: '',
'organization' => $request->get('organization') ?: '',
'section' => $request->get('section') ?: '',
'email' => $request->get('email') ?: '',
'signed' => FALSE);
if($database->query($engine, $query, $args) === FALSE)
return $this->_insertCleanup($engine);
//directories
if($this->createRoot($engine) === FALSE)
return $this->_insertCleanup($engine, TRUE);
//files
if($this->_insertIndex($engine) === FALSE
|| $this->_insertConfig($engine) === FALSE
|| $this->_insertSerial($engine) === FALSE)
return $this->_insertCleanup($engine, TRUE, TRUE);
//create certificate request
if($this->createCertificate($engine, $request, $parent,
$request->get('days'),
$request->get('keysize'), $error) === FALSE)
return $this->_insertCleanup($engine, TRUE, TRUE, TRUE);
//sign directly if requested
if($parent !== FALSE && $request->get('sign')
&& $parent->sign($engine, $this, $error)
=== FALSE)
return $this->_insertCleanup($engine, TRUE, TRUE, TRUE);
return TRUE;
}
protected function _insertCleanup(Engine $engine, $directories = FALSE,
$files = FALSE, $certificates = FALSE)
{
//FIXME really implement
return FALSE;
}
protected function _insertConfig(Engine $engine)
{
$from = array("\\", "\"", "$");
$to = array("\\\\", "\\\"", "\\$");
$root = $this->getRootCA($engine);
$filename = $root.'/openssl.cnf';
$module = $this->getModule()->getName();
$template = 'modules/'.$module.'/openssl.cnf.in';
if(($fp = fopen($filename, 'w')) === FALSE)
return FALSE;
ob_start();
$home = str_replace($from, $to, $root);
$title = str_replace($from, $to, $this->getTitle());
$res = include($template);
if($res !== FALSE)
$res = fwrite($fp, ob_get_contents());
ob_end_clean();
if(fclose($fp) === FALSE || $res === FALSE)
{
unlink($filename);
return FALSE;
}
return TRUE;
}
protected function _insertIndex(Engine $engine)
{
$root = $this->getRootCA($engine);
return touch($root.'/index.txt');
}
protected function _insertSerial(Engine $engine)
{
$root = $this->getRootCA($engine);
$filename = $root.'/serial';
if(($fp = fopen($filename, 'w')) === FALSE)
return FALSE;
$res = fwrite($fp, "01\n");
if(fclose($fp) === FALSE || $res === FALSE)
{
unlink($filename);
return FALSE;
}
return TRUE;
}
protected function _saveUpdate(Engine $engine, Request $request = NULL,
&$error)
{
$database = $engine->getDatabase();
$query = static::$ca_query_update;
$args = array('ca_id' => $this->getID(),
'signed' => $this->get('signed') ? TRUE : FALSE);
if(parent::_saveUpdate($engine, $request, $error) === FALSE)
return FALSE;
return ($database->query($engine, $query, $args) !== FALSE)
? TRUE : FALSE;
}
//CAPKIContent::sign
public function sign(Engine $engine, $content = FALSE, &$error = FALSE)
{
$root = $this->getRootCA($engine);
if($content === FALSE)
return parent::sign($engine, FALSE, $error);
if($root === FALSE)
{
$error = _('Internal error');
return FALSE;
}
$opensslcnf = $root.'/openssl.cnf';
switch(get_class($content))
{
case 'CAPKIContent':
if(($root = $content->getRootCA($engine))
=== FALSE)
{
$error = _('Internal error');
return FALSE;
}
$extensions = 'v3_ca';
$in = $root.'/cacert.csr';
$out = $root.'/cacert.pem';
break;
case 'CAClientPKIContent':
$extensions = 'usr_cert';
$in = $root.'/newreqs/'.$content->getTitle().'.csr';
$out = $root.'/certs/'.$content->getTitle().'.pem';
break;
case 'CAServerPKIContent':
$extensions = 'srv_cert';
$in = $root.'/newreqs/'.$content->getTitle().'.csr';
$out = $root.'/certs/'.$content->getTitle().'.pem';
break;
default:
$error = _('Invalid class to sign');
return FALSE;
}
if(!file_exists($in) || file_exists($out))
{
$error = _('Could not generate the signed certificate');
return $engine->log(LOG_ERR,
'Could not generate the signed certificate');
}
$cmd = 'openssl ca -batch'
.' -config '.escapeshellarg($opensslcnf)
.' -extensions '.escapeshellarg($extensions)
.' -policy policy_anything'
.' -out '.escapeshellarg($out)
.' -infiles '.escapeshellarg($in);
$res = -1;
$engine->log(LOG_DEBUG, 'Executing: '.$cmd);
$stdout = defined('STDOUT')
? STDOUT : fopen('php://stdout', 'w');
$fds = array(1 => $stdout, 2 => $stdout);
if(($fp = proc_open($cmd, $fds, $pipes)) === FALSE
|| ($res = proc_close($fp)) != 0)
{
$error = _('Could not sign the certificate request');
return FALSE;
}
$content->set('revoked', FALSE);
$content->set('signed', TRUE);
$content->save($engine);
return TRUE;
}
//protected
//properties
static protected $class = 'CAPKIContent';
static protected $list_order = 'title ASC';
//queries
//IN: ca_id
// parent
// country
// state
// locality
// organization
// section
// email
// signed
static protected $ca_query_insert = 'INSERT INTO daportal_ca (
ca_id, parent, country, state, locality, organization, section,
email, signed) VALUES (:ca_id, :parent, :country, :state,
:locality, :organization, :section, :email, :signed)';
//IN: ca_id
// signed
static protected $ca_query_update = 'UPDATE daportal_ca
SET signed=:signed WHERE ca_id=:ca_id';
//IN: module_id
static protected $query_list = 'SELECT content_id AS id, timestamp,
module_id, module, user_id, username, group_id, groupname,
title, content, enabled, public,
country, state, locality, organization, section, email, signed
FROM daportal_content_public, daportal_ca
WHERE daportal_content_public.content_id=daportal_ca.ca_id
AND module_id=:module_id';
//IN: module_id
// group_id
static protected $query_list_group = 'SELECT content_id AS id,
timestamp, module_id, module, user_id, username,
group_id, groupname, title, content, enabled, public,
country, state, locality, organization, section, email, signed
FROM daportal_content_public, daportal_ca
WHERE daportal_content_public.content_id=daportal_ca.ca_id
AND module_id=:module_id
AND daportal_content_public.user_id=daportal_user_group.user_id
AND daportal_user_group.group_id=daportal_group_enabled.group_id
AND (daportal_user_group.group_id=:group_id
OR daportal_content_public.group_id=:group_id)';
//IN: module_id
// user_id
static protected $query_list_user = 'SELECT content_id AS id, timestamp,
module_id, module, user_id, username, group_id, groupname,
title, content, enabled, public,
country, state, locality, organization, section, email, signed
FROM daportal_content_public, daportal_ca
WHERE daportal_content_public.content_id=daportal_ca.ca_id
AND module_id=:module_id
AND user_id=:user_id';
//IN: module_id
// user_id
static protected $query_list_user_private = 'SELECT content_id AS id,
timestamp, module_id, module, user_id, username,
group_id, groupname, title, content, enabled, public,
country, state, locality, organization, section, email, signed
FROM daportal_content_enabled, daportal_ca
WHERE daportal_content_enabled.content_id=daportal_ca.ca_id
AND module_id=:module_id
AND user_id=:user_id';
//IN: module_id
// user_id
// content_id
static protected $query_load = "SELECT content_id AS id, timestamp,
module_id, module, user_id, username, group_id, groupname,
title, content, enabled, public,
country, state, locality, organization, section, email, signed
FROM daportal_content_enabled, daportal_ca
WHERE daportal_content_enabled.content_id=daportal_ca.ca_id
AND module_id=:module_id
AND (public='1' OR user_id=:user_id)
AND content_id=:content_id";
//IN: module_id
// title
// parent
static protected $query_load_by_title_parent = 'SELECT content_id AS id,
timestamp, module_id, module, user_id, username,
group_id, groupname, title, content, enabled, public,
country, state, locality, organization, section, email, signed
FROM daportal_content_public, daportal_ca
WHERE daportal_content_public.content_id=daportal_ca.ca_id
AND module_id=:module_id AND title=:title AND parent=:parent';
//IN: module_id
// title
static protected $query_load_by_title_parent_null = 'SELECT content_id AS id,
timestamp, module_id, module, user_id, username,
group_id, groupname, title, content, enabled, public,
country, state, locality, organization, section, email, signed
FROM daportal_content_public, daportal_ca
WHERE daportal_content_public.content_id=daportal_ca.ca_id
AND module_id=:module_id AND title=:title AND parent IS NULL';
}
?>