mirror of https://github.com/MISP/MISP
Added client SSL certificate authentication as a CakePHP plugin
parent
c3ea6e4915
commit
d0c3b44e34
|
@ -108,6 +108,12 @@ CakePlugin::load('Assets'); // having Logable
|
|||
CakePlugin::load('SysLogLogable');
|
||||
CakePlugin::load('UrlCache');
|
||||
|
||||
/**
|
||||
* Uncomment the following line to enable client SSL certificate authentication.
|
||||
* It's also necessary to configure the plugin — for more information, please read app/Plugin/CertAuth/reame.md
|
||||
*/
|
||||
// CakePlugin::load('CertAuth');
|
||||
|
||||
/**
|
||||
* You can attach event listeners to the request lifecyle as Dispatcher Filter . By Default CakePHP bundles two filters:
|
||||
*
|
||||
|
|
|
@ -6,6 +6,7 @@ $config = array (
|
|||
'level' => 'medium',
|
||||
'salt' => 'Rooraenietu8Eeyo<Qu2eeNfterd-dd+',
|
||||
'cipherSeed' => '',
|
||||
//'auth'=>array('CertAuth.Certificate'), // additional authentication methods
|
||||
),
|
||||
'MISP' =>
|
||||
array (
|
||||
|
@ -48,4 +49,36 @@ $config = array (
|
|||
'amount' => 5,
|
||||
'expire' => 300,
|
||||
),
|
||||
// Uncomment the following to enable client SSL certificate authentication
|
||||
/*
|
||||
'CertAuth' =>
|
||||
array(
|
||||
'register'=>true,
|
||||
'ca'=>'FIRST.Org',
|
||||
'caId'=>'org',
|
||||
'mapCa'=>array(
|
||||
'O'=>'org',
|
||||
'emailAddress'=>'email',
|
||||
),
|
||||
'map'=>array(
|
||||
'O'=>'org',
|
||||
'emailAddress'=>'email',
|
||||
),
|
||||
'userModel'=>'User',
|
||||
'userModelKey'=>'nids_sid',
|
||||
'enableSession' => true,
|
||||
'syncUser'=>true,
|
||||
'restApi'=>array(
|
||||
'url'=>'https://www.first.org/data/members?scope=full&limit=1',
|
||||
'headers'=>array(),
|
||||
'param'=>array('email'=>'email'),
|
||||
'map'=>array(
|
||||
'uid'=>'nids_sid',
|
||||
'team'=>'org',
|
||||
'email'=>'email',
|
||||
'pgp_public'=>'gpgkey',
|
||||
),
|
||||
),
|
||||
),
|
||||
*/
|
||||
);
|
||||
|
|
|
@ -102,6 +102,13 @@ class AppController extends Controller {
|
|||
throw new ForbiddenException('The authentication key provided cannot be used for syncing.');
|
||||
}
|
||||
}
|
||||
} else if(!$this->Session->read('Auth.User')) {
|
||||
// load authentication plugins from Configure::read('Security.auth')
|
||||
$auth = Configure::read('Security.auth');
|
||||
if($auth) {
|
||||
$this->Auth->authenticate = array_merge($auth, $this->Auth->authenticate);
|
||||
$this->Auth->startup($this);
|
||||
}
|
||||
}
|
||||
// user must accept terms
|
||||
//
|
||||
|
|
|
@ -0,0 +1,262 @@
|
|||
<?php
|
||||
/**
|
||||
* Client SSL Certificate Authentication component
|
||||
*
|
||||
* Authorizes users based on their SSL credentials.
|
||||
*
|
||||
* Copyright (c) FIRST.Org, Inc. (https://first.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author Guilherme Capilé, Tecnodesign (https://tecnodz.com)
|
||||
* @copyright Copyright (c) FIRST.Org, Inc. (https://first.org)
|
||||
* @link http://github.com/FIRSTdotorg/cakephp-CertAuth
|
||||
* @package CertAuth.Certificate
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
App::uses('AuthComponent', 'Controller/Component');
|
||||
App::uses('BaseAuthenticate', 'Controller/Component/Auth');
|
||||
|
||||
class CertificateAuthenticate extends BaseAuthenticate
|
||||
{
|
||||
/**
|
||||
* Holds the certificate issuer information (available at SSL_CLIENT_I_DN)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $ca;
|
||||
|
||||
/**
|
||||
* Holds the certificate user information (available at SSL_CLIENT_S_DN)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $client;
|
||||
|
||||
/**
|
||||
* Holds the user information
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $user;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* This should only be called once per request, so it doesn't need to store values in
|
||||
* the instance. Simply checks if the certificate is valid (against configured valid issuers)
|
||||
* and returns the user information encoded.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
self::$ca = self::$client = false;
|
||||
|
||||
// this means the client certificate is valid — check nginx/apache configuration
|
||||
if(isset($_SERVER['SSL_CLIENT_VERIFY']) && $_SERVER['SSL_CLIENT_VERIFY']=='SUCCESS') {
|
||||
|
||||
if(isset($_SERVER['SSL_CLIENT_I_DN'])) {
|
||||
$CA = self::parse($_SERVER['SSL_CLIENT_I_DN'], Configure::read('CertAuth.mapCa'));
|
||||
|
||||
// only valid CAs, if this was configured
|
||||
if($ca=Configure::read('CertAuth.ca')) {
|
||||
$k = Configure::read('CertAuth.caId');
|
||||
if(!$k) $k = 'CN';
|
||||
$id = (isset($CA[$k]))?($CA[$k]):(false);
|
||||
|
||||
if(!$id) {
|
||||
$CA = false;
|
||||
} else if(is_array($ca)) {
|
||||
if(!in_array($id, $ca)) $CA = false;
|
||||
} else if($ca!=$id) {
|
||||
$CA = false;
|
||||
}
|
||||
unset($id, $k);
|
||||
}
|
||||
self::$ca = $CA;
|
||||
unset($CA, $ca);
|
||||
}
|
||||
|
||||
if(self::$ca && isset($_SERVER['SSL_CLIENT_S_DN'])) {
|
||||
self::$client = self::parse($_SERVER['SSL_CLIENT_S_DN'], Configure::read('CertAuth.map'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse certificate extensions
|
||||
*
|
||||
* @TODO this should properly address the RFC
|
||||
* @param string $s text to be parsed
|
||||
* @param (optional) array $map array of mapping extension to User fields
|
||||
* @return array parsed values
|
||||
*/
|
||||
private static function parse($s, $map=null)
|
||||
{
|
||||
$r=array();
|
||||
foreach(preg_split('#/#', $s, null, PREG_SPLIT_NO_EMPTY) as $v) {
|
||||
if($p=strpos($v, '=')) {
|
||||
$k = substr($v, 0, $p);
|
||||
if($map) {
|
||||
if(isset($map[$k])) {
|
||||
$k = $map[$k];
|
||||
} else {
|
||||
unset($p, $v, $k);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$r[$k] = substr($v, $p+1);
|
||||
}
|
||||
unset($p, $v, $k);
|
||||
}
|
||||
return $r;
|
||||
}
|
||||
|
||||
// to enable stateless authentication
|
||||
public function getUser(CakeRequest $request)
|
||||
{
|
||||
if(is_null(self::$user)) {
|
||||
if(self::$client) {
|
||||
self::$user = self::$client;
|
||||
|
||||
$sync = Configure::read('CertAuth.syncUser');
|
||||
|
||||
if($sync) {
|
||||
self::getRestUser();
|
||||
}
|
||||
|
||||
// find and fill user with model
|
||||
$cn = Configure::read('CertAuth.userModel');
|
||||
if($cn) {
|
||||
$k = Configure::read('CertAuth.userModelKey');
|
||||
if($k) {
|
||||
$q = array($k=>self::$user[$k]);
|
||||
} else {
|
||||
$q = self::$user;
|
||||
}
|
||||
$User = ClassRegistry::init($cn);
|
||||
$U = $User->find('first', array(
|
||||
'conditions' => $q,
|
||||
'recursive' => false
|
||||
));
|
||||
if($U) {
|
||||
if($sync) {
|
||||
$write = array();
|
||||
foreach(self::$user as $k=>$v) {
|
||||
if(array_key_exists($k, $U[$cn]) && trim($U[$cn][$k])!=trim($v)) {
|
||||
$write[] = $k;
|
||||
$U[$cn][$k] = trim($v);
|
||||
}
|
||||
unset($k, $v);
|
||||
}
|
||||
if($write && !$User->save($U[$cn], true, $write)) {
|
||||
CakeLog::write('alert', 'Could not update model at database with RestAPI data.');
|
||||
}
|
||||
unset($write);
|
||||
}
|
||||
self::$user = $U[$cn];
|
||||
} else if($sync) {
|
||||
$User->create();
|
||||
if($User->save(self::$user, true, array_keys(self::$user))) {
|
||||
$U = $User->read();
|
||||
self::$user = $U[$cn];
|
||||
} else {
|
||||
CakeLog::write('alert', 'Could not insert model at database from RestAPI data.');
|
||||
}
|
||||
}
|
||||
unset($U, $User, $q, $k);
|
||||
}
|
||||
unset($cn);
|
||||
}
|
||||
}
|
||||
|
||||
if(Configure::read('CertAuth.enableSession')) {
|
||||
if(!CakeSession::read(AuthComponent::$sessionKey)) {
|
||||
CakeSession::write(AuthComponent::$sessionKey, self::$user);
|
||||
}
|
||||
} else {
|
||||
AuthComponent::$sessionKey = false;
|
||||
}
|
||||
return self::$user;
|
||||
}
|
||||
|
||||
// to enable stateless authentication
|
||||
public function authenticate(CakeRequest $request, CakeResponse $response)
|
||||
{
|
||||
return self::getUser($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches user information from external REST API
|
||||
*
|
||||
* Valid options (should be configured under CertAuth.restApi):
|
||||
*
|
||||
* @param (optional) array $options API configuration
|
||||
* url (string) Where to fetch information from
|
||||
* headers (array) list of additional headers to be used, reserved for authentication tokens
|
||||
* params (array) mapping of additional params to be included at the url, uses $user values
|
||||
* map (array) mapping of the return values to be added to the self::$user
|
||||
* @return array updated user object
|
||||
*/
|
||||
public function getRestUser($options=null, $user=null)
|
||||
{
|
||||
if(is_null($options)) {
|
||||
$options = Configure::read('CertAuth.restApi');
|
||||
}
|
||||
if(!is_null($user)) {
|
||||
self::$user = $user;
|
||||
}
|
||||
|
||||
if(!isset($options['url'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create a stream
|
||||
$req = array(
|
||||
'http'=>array(
|
||||
'method'=>'GET',
|
||||
'header'=>"Accept: application/json\r\n"
|
||||
),
|
||||
);
|
||||
if(isset($options['headers'])) {
|
||||
foreach($options['headers'] as $k=>$v) {
|
||||
if(is_int($k)) {
|
||||
$req['header'] .= "{$v}\r\n";
|
||||
} else {
|
||||
$req['header'] .= "{$k}: {$v}\r\n";
|
||||
}
|
||||
unset($k, $v);
|
||||
}
|
||||
}
|
||||
|
||||
$url = $options['url'];
|
||||
if(isset($options['param'])) {
|
||||
foreach($options['param'] as $k=>$v) {
|
||||
if(isset(self::$user[$v])) {
|
||||
$url .= ((strpos($url, '?'))?('&'):('?'))
|
||||
. $k . '=' . urlencode(self::$user[$v]);
|
||||
}
|
||||
unset($k, $v);
|
||||
}
|
||||
}
|
||||
$ctx = stream_context_create($req);
|
||||
$a = file_get_contents($url, false, $ctx);
|
||||
if(!$a) return null;
|
||||
|
||||
$A = json_decode($a, true);
|
||||
if(!isset($A['data'][0])) return false;
|
||||
if(isset($options['map'])) {
|
||||
foreach($options['map'] as $k=>$v) {
|
||||
if(isset($A['data'][0][$k])) {
|
||||
self::$user[$v] = $A['data'][0][$k];
|
||||
}
|
||||
unset($k, $v);
|
||||
}
|
||||
}
|
||||
|
||||
return self::$user;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
#Client SSL Certificate Authentication for CakePHP
|
||||
|
||||
This plugin enables CakePHP applications to use client SSL certificates to stateless authenticate its users. It reads information from the client certificate and can synchronize data with a foreign REST API and the client User model.
|
||||
|
Loading…
Reference in New Issue