* @category Horde * @license http://www.horde.org/licenses/lgpl21 LGPL-2.1 * @package Auth * @since 2.1.0 */ /** * The Horde_Auth_X509 class provides an authentication driver for using X509 * client certificates. Since X509 certificates do not provide the password, * if the server setup requires the use of per-user passwords, a callback * function may be passed to obtain it from. * * @author Michael J Rubinsky * @category Horde * @copyright 2013-2017 Horde LLC * @license http://www.horde.org/licenses/lgpl21 LGPL-2.1 * @package Auth * @since 2.1.0 */ class Horde_Auth_X509 extends Horde_Auth_Base { /** * An array of capabilities, so that the driver can report which * operations it supports and which it doesn't. * * @var array */ protected $_capabilities = array( 'transparent' => true ); /** * Constructor. * * @param array $params Parameters: * - password: (string) If available, the password to use for the session. * DEFAULT: no password used. * - username_field: (string) Name of the $_SERVER field that * the username can be found in. DEFAULT: 'SSL_CLIENT_S_DN_EMAILADDRESS'. * - certificate_field: (string) Name of the $_SERVER field that contains * the full certificate. DEFAULT: 'SSL_CLIENT_CERT' * - ignore_purpose: (boolean) If true, will ignore any usage restrictions * on the presented client certificate. I.e., if openssl_x509_checkpurpose * returns false, authentication may still proceed. DEFAULT: false - ONLY * ENABLE THIS IF YOU KNOW WHY YOU ARE DOING SO. * - filter: (array) An array where the keys are field names and the * values are the values those certificate fields MUST * match to be considered valid. Keys in the format of * fieldone:fieldtwo will be taken as parent:child. * DEFAULT: no additionachecks applied. * * @throws InvalidArgumentException */ public function __construct(array $params = array()) { $params = array_merge(array( 'password' => false, 'username_field' => 'SSL_CLIENT_S_DN_CN', 'certificate_field' => 'SSL_CLIENT_CERT', 'ignore_purpose' => true, 'filter' => array() ), $params); parent::__construct($params); } /** * Not implemented. * * @param string $userId The userID to check. * @param array $credentials An array of login credentials. * * @throws Horde_Auth_Exception */ protected function _authenticate($userId, $credentials) { throw new Horde_Auth_Exception('Unsupported.'); } /** * Automatic authentication: checks if the username is set in the * configured header. * * @return boolean Whether or not the client is allowed. */ public function transparent() { if (!is_callable('openssl_x509_parse')) { throw new Horde_Auth_Exception('SSL not enabled on server.'); } if (empty($_SERVER[$this->_params['username_field']]) || empty($_SERVER[$this->_params['certificate_field']])) { return false; } // Valid for client auth? $cert = openssl_x509_read($_SERVER[$this->_params['certificate_field']]); if (!$this->_params['ignore_purpose'] && !openssl_x509_checkpurpose($cert, X509_PURPOSE_SSL_CLIENT) && !openssl_x509_checkpurpose($cert, X509_PURPOSE_ANY)) { return false; } $c_parsed = openssl_x509_parse($cert); foreach ($this->_params['filter'] as $key => $value) { $keys = explode(':', $key); $c = $c_parsed; foreach ($keys as $k) { $c = $c[$k]; } if ($c != $value) { return false; } } // Handle any custom validation added by sub classes. if (!$this->_validate($cert)) { return false; } // Free resources. openssl_x509_free($cert); // Set credentials $this->setCredential('userId', $_SERVER[$this->_params['username_field']]); $cred = array('certificate_id' => $c_parsed['hash']); if (!empty($this->_params['password'])) { $cred['password'] = $this->_params['password']; } $this->setCredential('credentials', $cred); return true; } /** * Perform additional validation of certificate fields. * * @return boolean */ protected function _validate($certificate) { return true; } }