From 8d71460121b19afd7fefd311a94cd26998017d07 Mon Sep 17 00:00:00 2001 From: Deon George Date: Thu, 9 Oct 2014 23:23:02 +1100 Subject: [PATCH] Updates to SSL and other general items --- application/classes/Config.php | 2 +- .../classes/Controller/User/Welcome.php | 14 +- application/classes/Kohana.php | 72 ++++ application/classes/Model/Account.php | 4 + application/classes/Model/SSL.php | 25 ++ application/config/config.php | 2 +- .../media/theme/focusbusiness/css/custom.css | 40 ++ .../media/theme/focusbusiness/img/title.png | Bin 0 -> 27931 bytes application/messages/models/ssl.php | 18 + application/views/pages/welcome.php | 70 +++- application/views/ssl/user/add.php | 7 +- application/views/ssl/user/view.php | 110 ++++++ application/views/welcome/user/shortcuts.php | 5 + modules/lnapp | 2 +- modules/ssl/classes/Controller/Admin/Ssl.php | 2 +- modules/ssl/classes/Controller/SSL.php | 14 - modules/ssl/classes/Controller/Ssl.php | 27 ++ modules/ssl/classes/Controller/User/Ssl.php | 53 ++- modules/ssl/classes/Model/SSL.php | 337 +---------------- modules/ssl/classes/lnApp/Model/SSL.php | 350 ++++++++++++++++++ modules/ssl/messages/models/ssl.php | 18 + modules/ssl/messages/user/ssl/add.php | 18 + modules/ssl/views/ssl/admin/add_edit.php | 2 + modules/ssl/views/ssl/user/add.php | 5 +- modules/ssl/views/ssl/user/view.php | 7 +- 25 files changed, 813 insertions(+), 391 deletions(-) create mode 100644 application/classes/Kohana.php create mode 100644 application/classes/Model/SSL.php create mode 100644 application/media/theme/focusbusiness/css/custom.css create mode 100644 application/media/theme/focusbusiness/img/title.png create mode 100644 application/messages/models/ssl.php create mode 100644 application/views/ssl/user/view.php create mode 100644 application/views/welcome/user/shortcuts.php delete mode 100644 modules/ssl/classes/Controller/SSL.php create mode 100644 modules/ssl/classes/Controller/Ssl.php create mode 100644 modules/ssl/classes/lnApp/Model/SSL.php create mode 100644 modules/ssl/messages/models/ssl.php create mode 100644 modules/ssl/messages/user/ssl/add.php 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 0000000000000000000000000000000000000000..1d73d436b23b0b78f4840ddae0ff3480269c12e3 GIT binary patch literal 27931 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 literal 0 HcmV?d00001 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 @@ + 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 ?> - -