diff --git a/application/classes/Config.php b/application/classes/Config.php index e66aba2..7d5f3ee 100644 --- a/application/classes/Config.php +++ b/application/classes/Config.php @@ -39,7 +39,7 @@ class Config extends Kohana_Config { } public static function Copywrite($html=FALSE) { - return ($html ? '©' : '(c)').' 2014 Deon George'; + return ($html ? '©' : '(c)').' 2014 IBM'; } public static function version() { diff --git a/application/classes/Controller/User/Welcome.php b/application/classes/Controller/User/Welcome.php index 6d5cf5b..145ed92 100644 --- a/application/classes/Controller/User/Welcome.php +++ b/application/classes/Controller/User/Welcome.php @@ -17,6 +17,12 @@ class Controller_User_Welcome extends Controller_Welcome { ); public function action_index() { + Block::factory() + ->title('Quick Shortcuts') + ->title_icon('icon-bookmark') + ->span(3) + ->body(View::factory('welcome/user/shortcuts')); + $n = ORM::factory('ADMIN')->where('EMAIL_ADDRESS','=',$this->ao->email)->find_all(); if (! $n->count()) $output = 'You have no currently registered ADMINs, would you like to '.HTML::anchor(URL::link('user','admin/add'),'Register').' one?'; @@ -84,14 +90,6 @@ class Controller_User_Welcome extends Controller_Welcome { ->title_icon('icon-info-sign') ->span(9) ->body($output); - -/* - Block::factory() - ->title('Quick Shortcuts') - ->title_icon('icon-bookmark') - ->span(3) - ->body(View::factory('welcome/user/shortcuts')); -*/ } } ?> diff --git a/application/classes/Kohana.php b/application/classes/Kohana.php new file mode 100644 index 0000000..9bdd9b6 --- /dev/null +++ b/application/classes/Kohana.php @@ -0,0 +1,72 @@ + diff --git a/application/classes/Model/Account.php b/application/classes/Model/Account.php index 5478069..08dca54 100644 --- a/application/classes/Model/Account.php +++ b/application/classes/Model/Account.php @@ -20,5 +20,9 @@ class Model_Account extends lnApp_Model_Account { return strlen($this->prefix) > 1 ? $this->prefix : sprintf('%s%06d',$this->prefix,$this->id); } + + public function ssl_dn() { + return sprintf('O=IBM,CN=%s',$this->id()); + } } ?> diff --git a/application/classes/Model/SSL.php b/application/classes/Model/SSL.php new file mode 100644 index 0000000..7773cbe --- /dev/null +++ b/application/classes/Model/SSL.php @@ -0,0 +1,25 @@ +array( + array(array($this,'isValidDN')), + ), + )); + } + + public function isValidDN() { + return ! $this->isCA() AND ($this->account->ssl_dn() == self::_dn(openssl_csr_get_subject($this->csr))); + } +} +?> diff --git a/application/config/config.php b/application/config/config.php index 014c986..becd59b 100644 --- a/application/config/config.php +++ b/application/config/config.php @@ -3,6 +3,6 @@ return array ( 'appname' => 'TSM Access Request', - 'theme' => 'bootstrap', + 'theme' => 'focusbusiness', 'theme_admin' => 'baseadmin', ); diff --git a/application/media/theme/focusbusiness/css/custom.css b/application/media/theme/focusbusiness/css/custom.css new file mode 100644 index 0000000..928f039 --- /dev/null +++ b/application/media/theme/focusbusiness/css/custom.css @@ -0,0 +1,40 @@ +#header { + padding: 25px 0; +} + +#header h1 { + width: 300px; + height: 80px; + background-size: 40% 80%; + + line-height: 120px; + padding-left: 8px; + + font-size: 12px; +} + +#header h1 a { + color: #000; +} + +#header h1 a:hover { + text-decoration: none; +} + +#header h1 a sup { + color: #F90; + padding: 5px; + font-size: 10px; +} + +#footer #footer-logo { + height: 65px; +} + +.modal-dialog { + height: 400px; +} + +#wrapper { + width: 980px; +} diff --git a/application/media/theme/focusbusiness/img/title.png b/application/media/theme/focusbusiness/img/title.png new file mode 100644 index 0000000..1d73d43 Binary files /dev/null and b/application/media/theme/focusbusiness/img/title.png differ diff --git a/application/messages/models/ssl.php b/application/messages/models/ssl.php new file mode 100644 index 0000000..8b693aa --- /dev/null +++ b/application/messages/models/ssl.php @@ -0,0 +1,18 @@ +array( + 'isValidDN'=>'This Certificate Sign Request does not contact your valid DN', + ), +); +?> diff --git a/application/views/pages/welcome.php b/application/views/pages/welcome.php index 64f5442..5052674 100644 --- a/application/views/pages/welcome.php +++ b/application/views/pages/welcome.php @@ -1,11 +1,71 @@
+
+
+

TSM Server Access Request.

+
+
+
-
-

Request Access

-

To be able to use this server, you need to request access. Once your access is activated, you will be able to use this TSM server with your TSM client.

-

Login »

-
+
+
+

// This Service

+
+
+
+

Node Access

+

This TSM server is available for you to connect with any TSM client and backup data. You do not need to worry about the setup of a TSM server (we've done already)!

+

When connecting to this server, you can try out a TSM client and see how you can protect your application or data, and how you can recover it.

+

Request as many NODE IDs as you need, it's easy, first just register on the site, and then drop an email to dgeorge AT au DOT ibm DOT com, and tell us what you need. We'll reply with the details.

+

Login »

+
+
+ +
+
+

Admin Access

+

If you would like to have ADMINISTRATOR access to this server, you can request that as well. With ADMIN access, you can experience running some commands on our TSM server, as well as use the TSM server HELP.

+

Admin access is via our Operations Center by default, however, if you want to try out admin access with an admin client, you'll need to request an SSL certificate.

+

Request SSL »

+
+
+ +
+
+

Want some guidance?

+

If you would like help with any of our clients, then please contact us and we'll be happy to help.

+

Send an email to dgeorge AT au DOT ibm DOT com, and we'll find somebody in your time zone to help you.

+

Login »

+
+
+ +
+ +
diff --git a/application/views/ssl/user/add.php b/application/views/ssl/user/add.php index 590fb82..06a6900 100644 --- a/application/views/ssl/user/add.php +++ b/application/views/ssl/user/add.php @@ -16,10 +16,11 @@ gsk8capicmd_64 -keydb -create -db dsmcert.kdb -type kdb -stash

  • Create a Certificate Signing Request using the following command:
    - gsk8capicmd_64 -certreq -create -db dsmcert.kdb -stashed -label 'TSM-SL01' -dn 'O=IBM,cn=id(); ?>' -size 2048 -file id(); ?>.CSR

    + gsk8capicmd_64 -certreq -create -db dsmcert.kdb -stashed -label 'TSM-SL01' -dn 'ssl_dn(); ?>' -size 2048 -file id(); ?>.CSR

  • -
  • Paste the contents of your CSR file here:
    +
  • Upload your CSR file here 'col-md-3','label'=>'CSR File')); ?> + OR, paste the contents of your CSR file here:
    'col-md-6','label'=>'CSR','placeholder'=>'Certificate Sign Request','style'=>'font-family: monospace;','cols'=>61,'rows'=>15)); ?>
  • @@ -31,5 +32,5 @@
    -
    + diff --git a/application/views/ssl/user/view.php b/application/views/ssl/user/view.php new file mode 100644 index 0000000..337705f --- /dev/null +++ b/application/views/ssl/user/view.php @@ -0,0 +1,110 @@ +
    + SSL Details + +
    +
    Subject
    +
    dn(); ?>
    + + cert) : ?> +
    Subject Key ID
    +
    ski(); ?>
    + +
    Serial Number
    +
    serial(); ?>
    + +
    Issuer
    +
    + validCA()) : ?> + ca->id,$o->issuer()); ?> + + issuer(); ?> + +
    + +
    Issuer Key ID
    +
    aki_keyid(); ?>
    + +
    Issuer Serial
    +
    aki_serial(); ?>
    + +
    Valid From
    +
    valid_from(TRUE); ?>
    + +
    Valid To
    +
    valid_to(TRUE); ?>
    + +
    Hash
    +
    hash(); ?>
    + +
    Version
    +
    version(); ?>
    + +
    Algorithm
    +
    algorithm(); ?>
    + + +
    Status
    +
    Waiting to be signed.
    + +
    + + cert) : ?> +
    + Certificate Chain + + data($o->list_ca()) + ->columns(array( + 'id'=>'ID', + 'subject_cn()'=>'Cert', + 'valid_to(TRUE)'=>'Expires', + 'issuer_cn()'=>'Issuer', + )) + ->prepend(array( + 'id'=>array('url'=>URL::link('','ssl/download/')), + )); ?> + + +
    + +
    + Certificate + +
    cert ? $o->cert : $o->csr; ?>
    + + download_button(); + + if ($ao=Auth::instance()->get_user() AND ($ao->isAdmin()) AND $o->service->status AND ($o->valid_to()-(Kohana::$config->load('ssl.min_renew_days')*86400) <= time()) AND $o->service->paid_to() > time()) : + echo Form::open(URL::link('admin','ssl/renew/'.$o->service->id)); + echo Form::button('submit','Renew',array('class'=>'btn btn-primary')); + endif + ?> +
    + +
    + TSM Configuration + +

    To use this certificate with a Tivoli Storage Manager client, please do the following:

    +

    (If this certificate has just been renewed, you only need to jump to the last step.)

    +

      +
    1. Download this signed certificate and the CA certificates above.
    2. +
    3. Open up a command prompt, and depending on your operating system, change to your BA Client BIN directory. For example:
      +
      +
      Linux
      +
      cd /opt/tivoli/tsm/client/ba/bin
      +
      Windows
      +
      cd "C:\Program Files\Tivoli\TSM\baclient"
      +
      +
    4. +
    5. Import the ROOT SSL certificate above with the following command:
      + gsk8capicmd_64 -cert -add -db dsmcert.kdb -stashed -file [DOWNLOAD.CRT] -label [NAME OF ROOT CERTIFICATE]

      +
    6. +
    7. Import any additional CA certificates above with the following command:
      + gsk8capicmd_64 -cert -add -db dsmcert.kdb -stashed -file [DOWNLOAD.CRT] -label [NAME OF CERTIFICATE]

      +
    8. +
    9. Import this signed certificate with the following command:
      + gsk8capicmd_64 -cert -receive -db dsmcert.kdb -stashed -default_cert enabled -file [DOWNLOAD.CRT]

      +
    10. +
    +
    diff --git a/application/views/welcome/user/shortcuts.php b/application/views/welcome/user/shortcuts.php new file mode 100644 index 0000000..8d4b8bc --- /dev/null +++ b/application/views/welcome/user/shortcuts.php @@ -0,0 +1,5 @@ +
    + Create Node + Create SSL + Create Admin +
    diff --git a/modules/lnapp b/modules/lnapp index a7ed667..9302b51 160000 --- a/modules/lnapp +++ b/modules/lnapp @@ -1 +1 @@ -Subproject commit a7ed6672e18773ff07eb1c01fcf8e38b4828de11 +Subproject commit 9302b51ebb18a638a8ed1f87643d1d04e1e27649 diff --git a/modules/ssl/classes/Controller/Admin/Ssl.php b/modules/ssl/classes/Controller/Admin/Ssl.php index 1fad9f9..1b4cfb7 100644 --- a/modules/ssl/classes/Controller/Admin/Ssl.php +++ b/modules/ssl/classes/Controller/Admin/Ssl.php @@ -9,7 +9,7 @@ * @copyright (c) 2009-2014 Deon George * @license http://dev.leenooks.net/license.html */ -class Controller_Admin_SSL extends Controller_SSL { +class Controller_Admin_Ssl extends Controller_Ssl { protected $auth_required = TRUE; protected $secure_actions = array( diff --git a/modules/ssl/classes/Controller/SSL.php b/modules/ssl/classes/Controller/SSL.php deleted file mode 100644 index 6a142d1..0000000 --- a/modules/ssl/classes/Controller/SSL.php +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/modules/ssl/classes/Controller/Ssl.php b/modules/ssl/classes/Controller/Ssl.php new file mode 100644 index 0000000..54e797a --- /dev/null +++ b/modules/ssl/classes/Controller/Ssl.php @@ -0,0 +1,27 @@ +where('id','=',$this->request->param('id'))->or_where('name','=',$this->request->param('id'))->find(); + + if (! $so->loaded() OR ! $so->cert) + throw HTTP_Exception::factory(404,'SSL either doesnt exist'); + + $this->auto_render = FALSE; + $this->response->headers('Content-Type','plain/text'); + $this->response->headers('Content-Disposition','attachment; filename="'.$so->name.'.crt"'); + $this->response->body($so->cert); + } +} +?> diff --git a/modules/ssl/classes/Controller/User/Ssl.php b/modules/ssl/classes/Controller/User/Ssl.php index c689ddf..19b5732 100644 --- a/modules/ssl/classes/Controller/User/Ssl.php +++ b/modules/ssl/classes/Controller/User/Ssl.php @@ -9,7 +9,7 @@ * @copyright (c) 2009-2013 Deon George * @license http://dev.leenooks.net/license.html */ -class Controller_User_SSL extends Controller_SSL { +class Controller_User_Ssl extends Controller_Ssl { protected $auth_required = TRUE; protected $secure_actions = array( @@ -19,31 +19,58 @@ class Controller_User_SSL extends Controller_SSL { ); public function action_add() { - if ($this->request->post()) { - $so = ORM::factory('SSL'); + if ($this->request->post() OR $_FILES) { + if ($_FILES AND $this->request->post('csr')) + SystemMessage::add(array( + 'title'=>_('Validation failed'), + 'type'=>'info', + 'body'=>_('Only supply a CSR file OR the CSR text, not both!'), + )); - $so->account_id = (string)Auth::instance()->get_user(); + else { - // Set our values, so that our filters have data - $so->values($this->request->post()); - - $this->save($so); + $so = ORM::factory('SSL'); + $so->account_id = (string)Auth::instance()->get_user(); - if ($so->saved()) - HTTP::redirect(URL::link('user','ssl/view/'.$so->id)); + // Set our values, so that our filters have data + $so->values($this->request->post()); + + if ($_FILES) { + // Process upload + $files = Validation::factory($_FILES) + ->rule('csr_file','Upload::valid') + ->rule('csr_file','Upload::not_empty') + ->rule('csr_file','Upload::type',array(':value',array('csr'))) + ->rule('csr_file','Upload::size',array(':value','512K')); + + if ($files->check()) + foreach ($files->data() as $file) { + $so->csr = file_get_contents($file['tmp_name']); + break; + } + + if (! $so->csr) + throw HTTP_Exception::factory(501,'No CSR data :csr_file?',$files->errors('user/ssl/add')); + } + + $this->save($so); + + if ($so->saved()) + HTTP::redirect(URL::link('user','ssl/view/'.$so->id)); + } } Block::factory() ->type('form-horizontal') - ->title('Add/Edit Record') - ->title_icon('fa-wrench') + ->title('SSL Certificate') + ->title_icon('fa-certificate') ->body(View::factory('ssl/user/add')->set('o',$this->ao)); } public function action_download() { $passwd_len = Kohana::$config->load('ssl')->minpass_length; - $so = ORM::factory('SSL',$this->request->post('sid')); + $so = ORM::factory('SSL',$this->request->param('id')); if (! $so->loaded() OR ! Auth::instance()->authorised($so->account)) throw HTTP_Exception::factory(403,'SSL either doesnt exist, or you are not authorised to see it'); diff --git a/modules/ssl/classes/Model/SSL.php b/modules/ssl/classes/Model/SSL.php index 3342e2d..0b81344 100644 --- a/modules/ssl/classes/Model/SSL.php +++ b/modules/ssl/classes/Model/SSL.php @@ -1,339 +1,4 @@ array(), - ); - - protected $_has_one = array( - 'ca'=>array('model'=>'SSL_CA','far_key'=>'ssl_ca_id','foreign_key'=>'id'), - ); - - protected $_display_filters = array( - 'csr'=>array( - array('Model_SSL::subject_csr',array(':value')), - ), - 'cert'=>array( - array('Model_SSL::subject_cert',array(':value')), - ), - ); - - protected $_save_message = TRUE; - - /** - * Parse our AuthorityKeyIndentifier Extension to extract information - * @param $key Return just that index - */ - private function _aki($key=NULL) { - $result = array(); - - $aki = $this->_extensions('authorityKeyIdentifier'); - if (! $aki) - return ''; - - foreach (explode("\n",preg_replace("/\n$/",'',$aki)) as $x) { - if (! $x) - continue; - - if (strstr($x,':')) { - list($a,$b) = explode(':',$x,2); - $result[strtolower($a)] = $b; - } - } - - return is_null($key) ? $result : Arr::get($result,$key); - } - - /** - * This function will convert a large decimal number into hex - * @param $number Large decimal number - */ - private static function _dec_to_hex($number) { - $hex = array(); - - if ($number == 0) - return '00'; - - while ($number > 0) { - if ($number == 0) { - array_push($hex, '0'); - - } else { - $x = (int) ($number/16); - array_push($hex,strtoupper(dechex((int)($number-($x*16))))); - $number = $x; - } - } - - return preg_replace('/^:/','',preg_replace('/(..)/',":$1",implode(array_reverse($hex)))); - } - - /** - * Parse our Sign Certifcate to extract information - * @param $key Return just that index - */ - private function _details($key=NULL) { - if (! $this->cert) - return array(); - - return is_null($key) ? $this->_cert_details : Arr::get($this->_cert_details,$key,array()); - } - - private static function _dn(array $array) { - $result = ''; - $i = 0; - - foreach ($array as $k=>$v) { - if ($i++) - $result .= ','; - - $result .= sprintf('%s=%s',$k,$v); - } - - return $result; - } - - /** - * Parse our Sign Certifcate Extensions to extract information - * @param $key Return just that index - */ - protected function _extensions($key=NULL) { - $result = Arr::get($this->_cert_details,'extensions'); - - return is_null($key) ? $result : Arr::get($result,$key); - } - - // We want to inject the SSL object into this Model - protected function _load_values(array $values) { - parent::_load_values($values); - - $this->_cert_details = openssl_x509_parse($this->cert); - - return $this; - } - - public function aki_dirname() { - return $this->_aki('dirname'); - } - - public function aki_keyid() { - return $this->_aki('keyid'); - } - - public function aki_serial() { - return $this->_aki('serial'); - } - - public function algorithm() { - if (! $this->cert) - return NULL; - - $e = ''; - openssl_x509_export(openssl_x509_read($this->cert),$e,FALSE); - - // @todo There must be a nice way to get this? - return (preg_match('/^\s+Signature Algorithm:\s*(.*)\s*$/m',$e,$match)) ? $match[1] : _('Unknown'); - } - - public function download_button() { - if ($this->valid_to() < time()) - return ''; - - $passwd_len = Kohana::$config->load('ssl')->minpass_length; - - $output = Form::open(URL::link('user','ssl/download'),array('class'=>'form-inline','data-toggle'=>'validator','role'=>'form')); - $output .= Form::hidden('sid',$this->id); - - if ($passwd_len) { - $output .= '
    '; - $output .= '
    '; - $output .= Form::password('passwd','',array('class'=>'form-control','placeholder'=>_('Choose a password'),'nocg'=>TRUE,'pattern'=>'.{'.$passwd_len.',}','data-error'=>"Minimum ${passwd_len} characters",'required')); - $output .= ''; - } - - $output .= Form::button('download','Download',array('class'=>'btn btn-default','nocg'=>TRUE)); - - if ($passwd_len) { - $output .= ''; - $output .= '
    '; - $output .= '
    '; - $output .= '
    '; - } - - $output .= Form::close(); - - return $output; - } - - public function dn() { - return $this->display($this->signed() ? 'cert' : 'csr'); - } - - public function hash() { - return Arr::get($this->_cert_details,'hash'); - } - - public function issuer() { - return self::_dn(Arr::get($this->_cert_details,'issuer',array())); - } - - public function serial() { - return $this->_dec_to_hex(Arr::get($this->_cert_details,'serialNumber')); - } - - /** - * (Re)Sign an SSL Certificate - */ - public function sign($force=FALSE) { - $ssl_config = Kohana::$config->load('ssl'); - $ssl_conf = Kohana::find_file('config',$ssl_config->config,''); - $ssl_conf = array_pop($ssl_conf); - - // If our certificate is not old enough skip - if ($this->valid_to() > time()+$ssl_config->min_renew_days*86400 AND ! $force) - return FALSE; - - $today = mktime(0,0,0,date('n'),date('j'),date('Y')); - $days = (int)$this->account->renew; - - if (! $this->ca->pk) - throw HTTP_Exception::factory(400,'Unable to sign, missing Private Key for CA :ca',array(':ca'=>$this->ca->subject_cn())); - - $res = openssl_csr_sign($this->csr,$this->ca->cert,$this->ca->pk,$days,array( - 'config'=>$ssl_conf, - 'x509_extensions'=>'client', - 'digest_alg'=>'sha1', - ),time()); - - if ($res AND openssl_x509_export($res,$cert)) { - $this->cert = $cert; - $this->save(); - - return TRUE; - - } else { - /* - echo Debug::vars(array( - 'csr'=>$this->csr, - 'ca'=>$this->ca->cert, - 'caid'=>$this->ca->id, - 'days'=>$days, - 'ssl'=>$ssl_conf, - 'x509e'=>'client', - // 'command'=>sprintf('openssl ca -days %s -cert /tmp/ssl_ca.crt -keyfile /tmp/ssl_ca.key -out /tmp/ssl.crt -in /tmp/ssl.csr -extensions %s -config %s',$days,'client',$ssl_conf), - )); - - file_put_contents('/tmp/ssl.csr',$this->csr); - file_put_contents('/tmp/ssl_ca.key',$this->ca->pk); - file_put_contents('/tmp/ssl_ca.crt',$this->ca->cert); - */ - throw HTTP_Exception::factory(501,'Error Creating SSL Certificate :error',array(':error'=>openssl_error_string())); - } - } - - private function signed() { - return $this->_cert_details ? TRUE : FALSE; - } - - public function ski() { - return $this->_extensions('subjectKeyIdentifier'); - } - - public function issuer_cn() { - return Arr::get($this->cert ? Arr::get($this->_cert_details,'issuer') : array(),'CN'); - } - - public function subject_cn() { - return $this->cert ? Arr::get(Arr::get($this->_cert_details,'subject'),'CN') : self::subject_csr($this->csr); - } - - public static function subject_cert($cert) { - try { - return self::_dn(Arr::get(openssl_x509_parse($cert),'subject')); - } catch (exception $e) { - return 'Invalid Cert'; - } - } - - public static function subject_csr($csr) { - try { - return self::_dn(openssl_csr_get_subject($csr)); - } catch (exception $e) { - return 'Invalid CSR'; - } - } - - public function validCA($format=FALSE) { - return StaticList_YesNo::get(($this->_cert_details AND $this->ssl_ca_id AND $this->aki_keyid()==$this->ca->ski()) ? $this->ca->validParent() : FALSE,$format); - } - - public function valid_from($format=FALSE) { - $k = Arr::get($this->_cert_details,'validFrom_time_t'); - - if (! $k) - return NULL; - - return $format ? Site::Date($k) : $k; - } - - public function valid_to($format=FALSE) { - $k = Arr::get($this->_cert_details,'validTo_time_t'); - - if (! $k) - return NULL; - - return $format ? Site::Date($k) : $k; - } - - // If we change the SSL certificate, we need to reload our SSL object - public function values(array $values, array $expected = NULL) { - parent::values($values,$expected); - - if (array_key_exists('cert',$this->_changed)) - $this->_cert_details = openssl_x509_parse($this->cert); - - return $this; - } - - public function version() { - return Arr::get($this->_cert_details,'version'); - } - - public function list_ca() { - $result = array(); - - if (! $this->validCA()) - return $result; - - $x = $this; - while (! is_null($x->ssl_ca_id) AND $x->id != $x->ssl_ca_id AND $x=$x->ca) - array_push($result,$x); - - return $result; - } - - /** - * Return all our CA Certs for this certificate - */ - public function list_ca_crts() { - $result = array(); - - foreach ($this->list_ca() as $so) - array_push($result,$so->cert); - - return $result; - } -} +class Model_SSL extends lnApp_Model_SSL {} ?> diff --git a/modules/ssl/classes/lnApp/Model/SSL.php b/modules/ssl/classes/lnApp/Model/SSL.php new file mode 100644 index 0000000..413b7d8 --- /dev/null +++ b/modules/ssl/classes/lnApp/Model/SSL.php @@ -0,0 +1,350 @@ +array(), + ); + + protected $_has_one = array( + 'ca'=>array('model'=>'SSL_CA','far_key'=>'ssl_ca_id','foreign_key'=>'id'), + ); + + protected $_display_filters = array( + 'csr'=>array( + array('Model_SSL::subject_csr',array(':value')), + ), + 'cert'=>array( + array('Model_SSL::subject_cert',array(':value')), + ), + ); + + protected $_save_message = TRUE; + + public function rules() { + return Arr::merge(parent::rules(),array( + 'csr'=>array( + array(array($this,'isCSR')), + ), + )); + } + + /** + * Parse our AuthorityKeyIndentifier Extension to extract information + * @param $key Return just that index + */ + protected function _aki($key=NULL) { + $result = array(); + + $aki = $this->_extensions('authorityKeyIdentifier'); + if (! $aki) + return ''; + + foreach (explode("\n",preg_replace("/\n$/",'',$aki)) as $x) { + if (! $x) + continue; + + if (strstr($x,':')) { + list($a,$b) = explode(':',$x,2); + $result[strtolower($a)] = $b; + } + } + + return is_null($key) ? $result : Arr::get($result,$key); + } + + /** + * This function will convert a large decimal number into hex + * @param $number Large decimal number + */ + protected static function _dec_to_hex($number) { + $hex = array(); + + if ($number == 0) + return '00'; + + while ($number > 0) { + if ($number == 0) { + array_push($hex, '0'); + + } else { + $x = (int) ($number/16); + array_push($hex,strtoupper(dechex((int)($number-($x*16))))); + $number = $x; + } + } + + return preg_replace('/^:/','',preg_replace('/(..)/',":$1",implode(array_reverse($hex)))); + } + + /** + * Parse our Sign Certifcate to extract information + * @param $key Return just that index + */ + protected function _details($key=NULL) { + if (! $this->cert) + return array(); + + return is_null($key) ? $this->_cert_details : Arr::get($this->_cert_details,$key,array()); + } + + protected static function _dn(array $array) { + $result = ''; + $i = 0; + + foreach ($array as $k=>$v) { + if ($i++) + $result .= ','; + + $result .= sprintf('%s=%s',$k,$v); + } + + return $result; + } + + /** + * Parse our Sign Certifcate Extensions to extract information + * @param $key Return just that index + */ + protected function _extensions($key=NULL) { + $result = Arr::get($this->_cert_details,'extensions'); + + return is_null($key) ? $result : Arr::get($result,$key); + } + + // We want to inject the SSL object into this Model + protected function _load_values(array $values) { + parent::_load_values($values); + + $this->_cert_details = openssl_x509_parse($this->cert); + + return $this; + } + + public function aki_dirname() { + return $this->_aki('dirname'); + } + + public function aki_keyid() { + return $this->_aki('keyid'); + } + + public function aki_serial() { + return $this->_aki('serial'); + } + + public function algorithm() { + if (! $this->cert) + return NULL; + + $e = ''; + openssl_x509_export(openssl_x509_read($this->cert),$e,FALSE); + + // @todo There must be a nice way to get this? + return (preg_match('/^\s+Signature Algorithm:\s*(.*)\s*$/m',$e,$match)) ? $match[1] : _('Unknown'); + } + + public function download_button() { + if ($this->valid_to() < time()) + return ''; + + $passwd_len = Kohana::$config->load('ssl')->minpass_length; + + $output = Form::open(URL::link('user','ssl/download/'.$this->id),array('class'=>'form-inline','data-toggle'=>'validator','role'=>'form')); + + if ($passwd_len) { + $output .= '
    '; + $output .= '
    '; + $output .= Form::password('passwd','',array('class'=>'form-control','placeholder'=>_('Choose a password'),'nocg'=>TRUE,'pattern'=>'.{'.$passwd_len.',}','data-error'=>"Minimum ${passwd_len} characters",'required')); + $output .= ''; + } + + $output .= Form::button('download','Download',array('class'=>'btn btn-default','nocg'=>TRUE)); + + if ($passwd_len) { + $output .= ''; + $output .= '
    '; + $output .= '
    '; + $output .= '
    '; + } + + $output .= Form::close(); + + return $output; + } + + public function dn() { + return $this->display($this->signed() ? 'cert' : 'csr'); + } + + public function hash() { + return Arr::get($this->_cert_details,'hash'); + } + + public function isCSR() { + return openssl_csr_get_subject($this->csr); + } + + public function issuer() { + return self::_dn(Arr::get($this->_cert_details,'issuer',array())); + } + + public function serial() { + return $this->_dec_to_hex(Arr::get($this->_cert_details,'serialNumber')); + } + + /** + * (Re)Sign an SSL Certificate + */ + public function sign($force=FALSE) { + $ssl_config = Kohana::$config->load('ssl'); + $ssl_conf = Kohana::find_file('config',$ssl_config->config,''); + $ssl_conf = array_pop($ssl_conf); + + // If our certificate is not old enough skip + if ($this->valid_to() > time()+$ssl_config->min_renew_days*86400 AND ! $force) + return FALSE; + + $today = mktime(0,0,0,date('n'),date('j'),date('Y')); + $days = (int)$this->account->renew; + + if (! $this->ca->pk) + throw HTTP_Exception::factory(400,'Unable to sign, missing Private Key for CA :ca',array(':ca'=>$this->ca->subject_cn())); + + $res = openssl_csr_sign($this->csr,$this->ca->cert,$this->ca->pk,$days,array( + 'config'=>$ssl_conf, + 'x509_extensions'=>'client', + 'digest_alg'=>'sha1', + ),time()); + + if ($res AND openssl_x509_export($res,$cert)) { + $this->cert = $cert; + $this->save(); + + return TRUE; + + } else { + /* + echo Debug::vars(array( + 'csr'=>$this->csr, + 'ca'=>$this->ca->cert, + 'caid'=>$this->ca->id, + 'days'=>$days, + 'ssl'=>$ssl_conf, + 'x509e'=>'client', + // 'command'=>sprintf('openssl ca -days %s -cert /tmp/ssl_ca.crt -keyfile /tmp/ssl_ca.key -out /tmp/ssl.crt -in /tmp/ssl.csr -extensions %s -config %s',$days,'client',$ssl_conf), + )); + + file_put_contents('/tmp/ssl.csr',$this->csr); + file_put_contents('/tmp/ssl_ca.key',$this->ca->pk); + file_put_contents('/tmp/ssl_ca.crt',$this->ca->cert); + */ + throw HTTP_Exception::factory(501,'Error Creating SSL Certificate :error',array(':error'=>openssl_error_string())); + } + } + + protected function signed() { + return $this->_cert_details ? TRUE : FALSE; + } + + public function ski() { + return $this->_extensions('subjectKeyIdentifier'); + } + + public function issuer_cn() { + return Arr::get($this->cert ? Arr::get($this->_cert_details,'issuer') : array(),'CN'); + } + + public function subject_cn() { + return Arr::get($this->cert ? Arr::get($this->_cert_details,'subject') : openssl_csr_get_subject($this->csr),'CN'); + } + + public static function subject_cert($cert) { + try { + return self::_dn(Arr::get(openssl_x509_parse($cert),'subject')); + } catch (exception $e) { + return 'Invalid Cert'; + } + } + + public static function subject_csr($csr) { + try { + return self::_dn(openssl_csr_get_subject($csr)); + } catch (exception $e) { + return 'Invalid CSR'; + } + } + + public function validCA($format=FALSE) { + return StaticList_YesNo::get(($this->_cert_details AND $this->ssl_ca_id AND $this->aki_keyid()==$this->ca->ski()) ? $this->ca->validParent() : FALSE,$format); + } + + public function valid_from($format=FALSE) { + $k = Arr::get($this->_cert_details,'validFrom_time_t'); + + if (! $k) + return NULL; + + return $format ? Site::Date($k) : $k; + } + + public function valid_to($format=FALSE) { + $k = Arr::get($this->_cert_details,'validTo_time_t'); + + if (! $k) + return NULL; + + return $format ? Site::Date($k) : $k; + } + + // If we change the SSL certificate, we need to reload our SSL object + public function values(array $values, array $expected = NULL) { + parent::values($values,$expected); + + if (array_key_exists('cert',$this->_changed)) + $this->_cert_details = openssl_x509_parse($this->cert); + + return $this; + } + + public function version() { + return Arr::get($this->_cert_details,'version'); + } + + public function list_ca() { + $result = array(); + + if (! $this->validCA()) + return $result; + + $x = $this; + while (! is_null($x->ssl_ca_id) AND $x->id != $x->ssl_ca_id AND $x=$x->ca) + array_push($result,$x); + + return $result; + } + + /** + * Return all our CA Certs for this certificate + */ + public function list_ca_crts() { + $result = array(); + + foreach ($this->list_ca() as $so) + array_push($result,$so->cert); + + return $result; + } +} +?> diff --git a/modules/ssl/messages/models/ssl.php b/modules/ssl/messages/models/ssl.php new file mode 100644 index 0000000..b22e373 --- /dev/null +++ b/modules/ssl/messages/models/ssl.php @@ -0,0 +1,18 @@ +array( + 'isCSR'=>'This is not a valid Certificate Sign Request', + ), +); +?> diff --git a/modules/ssl/messages/user/ssl/add.php b/modules/ssl/messages/user/ssl/add.php new file mode 100644 index 0000000..c99274a --- /dev/null +++ b/modules/ssl/messages/user/ssl/add.php @@ -0,0 +1,18 @@ +array( + 'Upload::type'=>'Incorrect upload type, it must be CSR', + ), +); +?> diff --git a/modules/ssl/views/ssl/admin/add_edit.php b/modules/ssl/views/ssl/admin/add_edit.php index 0dbfd94..6948eec 100644 --- a/modules/ssl/views/ssl/admin/add_edit.php +++ b/modules/ssl/views/ssl/admin/add_edit.php @@ -69,6 +69,8 @@ Private Key Data pk,array('divclass'=>'col-md-12','placeholder'=>'Private Key','style'=>'font-family: monospace; font-size: 95%;','rows'=>Form::textarea_rows($o->pk))); ?> + + name,array('divclass'=>'col-md-6','label'=>'Name','placeholder'=>'Label')); ?>
    diff --git a/modules/ssl/views/ssl/user/add.php b/modules/ssl/views/ssl/user/add.php index 8d1aebc..77a4aad 100644 --- a/modules/ssl/views/ssl/user/add.php +++ b/modules/ssl/views/ssl/user/add.php @@ -4,7 +4,8 @@

    To use SSL with this application, you need to receive a certificate from this service.

    Please do the following:

      -
    1. Paste the contents of your CSR file here:
      +
    2. Upload your CSR file here 'col-md-3','label'=>'CSR File')); ?> + OR, paste the contents of your CSR file here:
      'col-md-6','label'=>'CSR','placeholder'=>'Certificate Sign Request','style'=>'font-family: monospace;','cols'=>61,'rows'=>15)); ?>
    3. @@ -16,5 +17,5 @@
      -
      + diff --git a/modules/ssl/views/ssl/user/view.php b/modules/ssl/views/ssl/user/view.php index e710293..2ec1a99 100644 --- a/modules/ssl/views/ssl/user/view.php +++ b/modules/ssl/views/ssl/user/view.php @@ -61,7 +61,7 @@ 'issuer_cn()'=>'Issuer', )) ->prepend(array( - 'id'=>array('url'=>URL::link('admin','ssl/edit/')), + 'id'=>array('url'=>URL::link('','ssl/download/')), )); ?>
    @@ -79,9 +79,4 @@ echo Form::button('submit','Renew',array('class'=>'btn btn-primary')); endif ?> - -