1<?php 2/** 3 * Copyright 2013-2017 Horde LLC (http://www.horde.org) 4 * 5 * See the enclosed file COPYING for license information (LGPL). If you did 6 * not receive this file, see http://www.horde.org/licenses/lgpl21. 7 * 8 * @author Michael J Rubinsky <mrubinsk@horde.org> 9 * @category Horde 10 * @license http://www.horde.org/licenses/lgpl21 LGPL-2.1 11 * @package Auth 12 * @since 2.1.0 13 */ 14 15/** 16 * The Horde_Auth_X509 class provides an authentication driver for using X509 17 * client certificates. Since X509 certificates do not provide the password, 18 * if the server setup requires the use of per-user passwords, a callback 19 * function may be passed to obtain it from. 20 * 21 * @author Michael J Rubinsky <mrubinsk@horde.org> 22 * @category Horde 23 * @copyright 2013-2017 Horde LLC 24 * @license http://www.horde.org/licenses/lgpl21 LGPL-2.1 25 * @package Auth 26 * @since 2.1.0 27 */ 28class Horde_Auth_X509 extends Horde_Auth_Base 29{ 30 /** 31 * An array of capabilities, so that the driver can report which 32 * operations it supports and which it doesn't. 33 * 34 * @var array 35 */ 36 protected $_capabilities = array( 37 'transparent' => true 38 ); 39 40 /** 41 * Constructor. 42 * 43 * @param array $params Parameters: 44 * - password: (string) If available, the password to use for the session. 45 * DEFAULT: no password used. 46 * - username_field: (string) Name of the $_SERVER field that 47 * the username can be found in. DEFAULT: 'SSL_CLIENT_S_DN_EMAILADDRESS'. 48 * - certificate_field: (string) Name of the $_SERVER field that contains 49 * the full certificate. DEFAULT: 'SSL_CLIENT_CERT' 50 * - ignore_purpose: (boolean) If true, will ignore any usage restrictions 51 * on the presented client certificate. I.e., if openssl_x509_checkpurpose 52 * returns false, authentication may still proceed. DEFAULT: false - ONLY 53 * ENABLE THIS IF YOU KNOW WHY YOU ARE DOING SO. 54 * - filter: (array) An array where the keys are field names and the 55 * values are the values those certificate fields MUST 56 * match to be considered valid. Keys in the format of 57 * fieldone:fieldtwo will be taken as parent:child. 58 * DEFAULT: no additionachecks applied. 59 * 60 * @throws InvalidArgumentException 61 */ 62 public function __construct(array $params = array()) 63 { 64 $params = array_merge(array( 65 'password' => false, 66 'username_field' => 'SSL_CLIENT_S_DN_CN', 67 'certificate_field' => 'SSL_CLIENT_CERT', 68 'ignore_purpose' => true, 69 'filter' => array() 70 ), $params); 71 72 parent::__construct($params); 73 } 74 75 /** 76 * Not implemented. 77 * 78 * @param string $userId The userID to check. 79 * @param array $credentials An array of login credentials. 80 * 81 * @throws Horde_Auth_Exception 82 */ 83 protected function _authenticate($userId, $credentials) 84 { 85 throw new Horde_Auth_Exception('Unsupported.'); 86 } 87 88 /** 89 * Automatic authentication: checks if the username is set in the 90 * configured header. 91 * 92 * @return boolean Whether or not the client is allowed. 93 */ 94 public function transparent() 95 { 96 if (!is_callable('openssl_x509_parse')) { 97 throw new Horde_Auth_Exception('SSL not enabled on server.'); 98 } 99 100 if (empty($_SERVER[$this->_params['username_field']]) || 101 empty($_SERVER[$this->_params['certificate_field']])) { 102 return false; 103 } 104 105 // Valid for client auth? 106 $cert = openssl_x509_read($_SERVER[$this->_params['certificate_field']]); 107 if (!$this->_params['ignore_purpose'] && 108 !openssl_x509_checkpurpose($cert, X509_PURPOSE_SSL_CLIENT) && 109 !openssl_x509_checkpurpose($cert, X509_PURPOSE_ANY)) { 110 return false; 111 } 112 113 $c_parsed = openssl_x509_parse($cert); 114 foreach ($this->_params['filter'] as $key => $value) { 115 $keys = explode(':', $key); 116 $c = $c_parsed; 117 foreach ($keys as $k) { 118 $c = $c[$k]; 119 } 120 if ($c != $value) { 121 return false; 122 } 123 } 124 125 // Handle any custom validation added by sub classes. 126 if (!$this->_validate($cert)) { 127 return false; 128 } 129 130 // Free resources. 131 openssl_x509_free($cert); 132 133 // Set credentials 134 $this->setCredential('userId', $_SERVER[$this->_params['username_field']]); 135 $cred = array('certificate_id' => $c_parsed['hash']); 136 if (!empty($this->_params['password'])) { 137 $cred['password'] = $this->_params['password']; 138 } 139 $this->setCredential('credentials', $cred); 140 141 return true; 142 } 143 144 /** 145 * Perform additional validation of certificate fields. 146 * 147 * @return boolean 148 */ 149 protected function _validate($certificate) 150 { 151 return true; 152 } 153 154} 155