Skip to content
Snippets Groups Projects
Commit d3578707 authored by Olav Morken's avatar Olav Morken
Browse files

New authentication module: authX509

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@2421 44740490-163a-0410-bde0-09ae8108e29a
parent 5155e483
No related branches found
No related tags found
No related merge requests found
......@@ -235,5 +235,23 @@
},
"descr_RESPONSESTATUSNOSUCCESS": {
"en": "The Identity Provider responded with an error. (The status code in the SAML Response was not success)"
},
"title_NOCERT": {
"en": "No certificate"
},
"descr_NOCERT": {
"en": "Authentication failed: your browser did not send any certificate"
},
"title_INVALIDCERT": {
"en": "Invalid certificate"
},
"descr_INVALIDCERT": {
"en": "Authentication failed: the certificate your browser sent is invalid or cannot be read"
},
"title_UNKNOWNCERT": {
"en": "Unknown certificate"
},
"descr_UNKNOWNCERT": {
"en": "Authentication failed: the certificate your browser sent is unknown"
}
}
......@@ -1433,5 +1433,23 @@
"pt": "O Fornecedor de Identidade respondeu com um erro. (A resposta SAML cont\u00e9m um c\u00f3digo de insucesso)",
"fr": "Le fournisseur d'identit\u00e9 a renvoy\u00e9 une erreur (le code de statut de la r\u00e9ponse SAML n'indiquait pas le succ\u00e8s)",
"hr": "Sustav jedinstvene autentikacije poslao je odgovor koji sadr\u017ei informaciju o pojavi gre\u0161ke. (\u0160ifra statusa dostavljena u SAML odgovoru ne odgovara \u0161ifri uspje\u0161no obra\u0111enog zahtjeva)"
},
"title_NOCERT": {
"fr": "Aucun certificat pr\u00e9sent\u00e9"
},
"descr_NOCERT": {
"fr": "Echec de l'authentification: votre navigateur n'a pas pr\u00e9sent\u00e9 de certificat"
},
"title_INVALIDCERT": {
"fr": "Certificat invalide"
},
"descr_INVALIDCERT": {
"fr": "Echec de l'authentification: le certificat pr\u00e9sent\u00e9 par votre navigateur est invalide ou illisible"
},
"title_UNKNOWNCERT": {
"fr": "Certificat inconnu"
},
"descr_UNKNOWNCERT": {
"fr": "Echec de l'authentification: le certificat pr\u00e9sent\u00e9 par votre navigateur n'est pas connu"
}
}
......@@ -28,6 +28,9 @@ Authentication module
The next step is to configure the way users authenticate on your IdP. Various modules in the `modules/` directory provides methods for authenticating your users. This is an overview of those that are included in the simpleSAMLphp distribution:
[`authX509:authX509userCert`](./authX509:authX509)
: Authenticate against a LDAP database with a SSL client certificate.
`exampleauth:UserPass`
: Authenticate against a list of usernames and passwords.
......
{
"certificate_header": {
"en": "X509 certificate authentication"
},
"certificate_text": {
"en": "X509 certificate authentication is required to access this service."
}
}
{
"certificate_header": {
"fr": "Authentification par certificat X509"
},
"certificate_text": {
"fr": "Un certificat X509 est requis pour acc\u00e9der \u00e0 ce service."
}
}
Using the X509 authentication source with simpleSAMLphp
=======================================================
The authX509 module provide X509 authentication with certificate
validation. For now there is only one authentication source:
* authX509userCert Validate against LDAP userCertificate attribute
More validation schemes (OCSP, CRL, local list) might be added later.
Configuring Apache
------------------
This module assumes that the server requests a client certificate, and
stores it in the environment variable SSL_CLIENT_CERT. This can be achieved
with such a configuration:
SSLEngine on
SSLCertificateFile /etc/openssl/certs/server.crt
SSLCertificateKeyFile /etc/openssl/private/server.key
SSLCACertificateFile /etc/openssl/certs/ca.crt
SSLVerifyClient require
SSLVerifyDepth 2
SSLOptions +ExportCertData
Note that SSLVerifyClient can be set to optional if you want to support
both certificate and plain login authentication at the same time (more on
this later).
If your server or your client (or both!) have TLS renegociation disabled
as a workaround for CVE-2009-3555, then the configuration directive above
must not appear in a <Directory>, <Location>, or in a name-based
<VirtualHost>. You can only use them server-wide, or in <VirtualHost>
with different IP address/port combinaisons.
Setting up the authX509 module
------------------------------
The first thing you need to do is to enable the cas module:
touch modules/authX509/enable
Then you must add it as an authentication source. Here is an
example authsource.php
'x509' => array(
'authX509:authX509userCert',
'hostname' => 'ldaps://ldap.example.net',
'enable_tls' => FALSE,
'attributes' => array("cn", "uid", "mail", "ou", "sn"),
'search.enable' => TRUE,
'search.attributes' => array('uid', 'mail'),
'search.base' => 'dc=example,dc=net',
'x509attributes' => array('UID' => 'uid'),
'ldapusercert' => array('userCertificate;binary'),
),
The configuration is the same as for the LDAP module, except for
two options:
* x509attributes is used to map a certificate subject attribute to
an LDAP attribute. It is used to find the certificate
owner in LDAP from the certificate subject. If multiple
mappings are provided, any mappping will match (this
is a logical OR). Default is array('UID' => 'uid')
* ldapusercert the LDAP attribute in which the user certificate will
be found. Default is userCertificate;binary
Uploading certificate in LDAP
-----------------------------
Certificate are usually stored in LDAP as DER, in binary. Here is
how to convert from PEM to DER:
openssl x509 -in cert.pem -inform PEM -outform DER -out cert.der
Here is some LDIF to upload the certificate in the directory:
dn: uid=jdoe,dc=example,dc=net
changetype: modify
add: userCertificate;binary
userCertificate;binary:< file:///path/to/cert.der
Supporting both certificate and login authentications
=====================================================
In your Apache configuration, set SSLVerifyClient to optional. Then you
can hack your metadata/saml20-idp-hosted.php file that way:
$auth_source = empty($_SERVER['SSL_CLIENT_CERT']) ? 'ldap' : 'x509';
$metadata = array(
'__DYNAMIC:1__' => array(
'host' => '__DEFAULT__',
'privatekey' => 'server.key',
'certificate' => 'server.crt',
'auth' => $auth_source,
'authority' => 'login',
'userid.attribute' => 'uid',
'logouttype' => 'iframe',
'AttributeNameFormat' =>
'urn:oasis:names:tc:SAML:2.0:attrname-format:uri',
)
<?php
/**
* This class implements x509 certificate authentication with
* certificate validation against an LDAP directory.
*
* @author Emmanuel Dreyfus <manu@netbsd.org>
* @package simpleSAMLphp
* @version $Id$
*/
class sspmod_authX509_Auth_Source_X509userCert extends SimpleSAML_Auth_Source {
/**
* x509 attributes to use from the certificate
* for searching the user in the LDAP directory.
*/
private $x509attributes = array('UID' => 'uid');
/**
* LDAP attribute containing the user certificate
*/
private $ldapusercert = array('userCertificate;binary');
/**
* LDAPConfigHelper object
*/
private $ldapcf;
/**
* Constructor for this authentication source.
*
* All subclasses who implement their own constructor must call this
* constructor before using $config for anything.
*
* @param array $info Information about this authentication source.
* @param array &$config Configuration for this authentication source.
*/
public function __construct($info, &$config) {
assert('is_array($info)');
assert('is_array($config)');
if (isset($config['authX509:x509attributes']))
$this->x509attributes =
$config['authX509:x509attributes'];
if (isset($config['authX509:ldapusercert']))
$this->ldapusercert =
$config['authX509:ldapusercert'];
parent::__construct($info, $config);
$this->ldapcf = new sspmod_ldap_ConfigHelper($config,
'Authentication source ' . var_export($this->authId, TRUE));
return;
}
/**
* Convert certificate from PEM to DER
*
* @param array $pem_data PEM-encoded certificate
*/
private function pem2der($pem_data) {
$begin = "CERTIFICATE-----";
$end = "-----END";
$pem_data = substr($pem_data,
strpos($pem_data, $begin)+strlen($begin));
$pem_data = substr($pem_data, 0, strpos($pem_data, $end));
$der = base64_decode($pem_data);
return $der;
}
/**
* Convert certificate from DER to PEM
*
* @param array $der_data DER-encoded certificate
*/
private function der2pem($der_data) {
$pem = chunk_split(base64_encode($der_data), 64, "\n");
$pem = "-----BEGIN CERTIFICATE-----\n".$pem.
"-----END CERTIFICATE-----\n";
return $pem;
}
/**
* Finish a failed authentication.
*
* This function can be overloaded by a child authentication
* class that wish to perform some operations on failure
*
* @param array &$state Information about the current authentication.
*/
public function authFailed(&$state) {
$id = SimpleSAML_Auth_State::saveState($state, self::STAGEID);
$params = array('AuthState' => $id);
$config = SimpleSAML_Configuration::getInstance();
$t = new SimpleSAML_XHTML_Template($config,
'authX509:X509error.php');
$t->data['stateparams'] = $params;
$t->data['errorcode'] = $state['authX509.error'];
$t->show();
exit();
}
/**
* Validate certificate and login
*
* This function try to validate the certificate.
* On success, the user is logged in without going through
* o login page.
* On failure, The authX509:X509error.php template is
* loaded.
*
* @param array &$state Information about the current authentication.
*/
public function authenticate(&$state) {
assert('is_array($state)');
$ldapcf = $this->ldapcf;
if (!isset($_SERVER['SSL_CLIENT_CERT']) ||
($_SERVER['SSL_CLIENT_CERT'] == '')) {
$state['authX509.error'] = "NOCERT";
$this->authFailed($state);
assert('FALSE'); /* NOTREACHED */
return;
}
$client_cert = $_SERVER['SSL_CLIENT_CERT'];
$client_cert_data = openssl_x509_parse($client_cert);
if ($client_cert_data == FALSE) {
SimpleSAML_Logger::error('authX509: invalid cert');
$state['authX509.error'] = "INVALIDCERT";
$this->authFailed($state);
assert('FALSE'); /* NOTREACHED */
return;
}
$dn = FALSE;
foreach ($this->x509attributes as $x509_attr => $ldap_attr) {
/* value is scalar */
$value = $client_cert_data['subject'][$x509_attr];
SimpleSAML_Logger::info('authX509: cert '.
$x509_attr.' = '.$value);
$dn = $ldapcf->searchfordn($ldap_attr, $value, TRUE);
if ($dn !== FALSE)
break;
}
if ($dn === FALSE) {
SimpleSAML_Logger::error('authX509: cert has '.
'no matching user in LDAP');
$state['authX509.error'] = "UNKNOWNCERT";
$this->authFailed($state);
assert('FALSE'); /* NOTREACHED */
return;
}
$ldap_certs = $ldapcf->getAttributes($dn, $this->ldapusercert);
if ($ldap_certs === FALSE) {
SimpleSAML_Logger::error('authX509: no certificate '.
'found in LDAP for dn='.$dn);
$state['authX509.error'] = "UNKNOWNCERT";
$this->authFailed($state);
assert('FALSE'); /* NOTREACHED */
return;
}
$merged_ldapcerts = array();
foreach ($this->ldapusercert as $attr)
$merged_ldapcerts = array_merge($merged_ldapcerts,
$ldap_certs[$attr]);
$ldap_certs = $merged_ldapcerts;
foreach ($ldap_certs as $ldap_cert) {
$pem = $this->der2pem($ldap_cert);
$ldap_cert_data = openssl_x509_parse($pem);
if($ldap_cert_data == FALSE) {
SimpleSAML_Logger::error('authX509: cert in '.
'LDAP in invalid for '.
'dn = '.$dn);
continue;
}
if ($ldap_cert_data === $client_cert_data) {
$attributes = $ldapcf->getAttributes($dn);
assert('is_array($attributes)');
$state['Attributes'] = $attributes;
$this->authSuccesful($state);
assert('FALSE'); /* NOTREACHED */
return;
}
}
SimpleSAML_Logger::error('authX509: no matching cert in '.
'LDAP for dn = '.$dn);
$state['authX509.error'] = "UNKNOWNCERT";
$this->authFailed($state);
assert('FALSE'); /* NOTREACHED */
return;
}
/**
* Finish a succesfull authentication.
*
* This function can be overloaded by a child authentication
* class that wish to perform some operations after login.
*
* @param array &$state Information about the current authentication.
*/
public function authSuccesful(&$state) {
SimpleSAML_Auth_Source::completeAuth($state);
assert('FALSE'); /* NOTREACHED */
return;
}
}
<?php
$this->data['header'] = $this->t('{authX509:X509error:certificate_header}');
$this->includeAtTemplateBase('includes/header.php');
?>
<?php
if ($this->data['errorcode'] !== NULL) {
?>
<div style="border-left: 1px solid #e8e8e8; border-bottom: 1px solid #e8e8e8; background: #f5f5f5">
<img src="/<?php echo $this->data['baseurlpath']; ?>resources/icons/experience/gtk-dialog-error.48x48.png" style="float: left; margin: 15px " />
<h2><?php echo $this->t('{login:error_header}'); ?></h2>
<p><b><?php echo $this->t('{errors:title_' . $this->data['errorcode'] . '}'); ?></b></p>
<p><?php echo $this->t('{errors:descr_' . $this->data['errorcode'] . '}'); ?></p>
</div>
<?php
}
?>
<h2 style="break: both"><?php echo $this->t('{authX509:X509error:certificate_header}'); ?></h2>
<p><?php echo $this->t('{authX509:X509error:certificate_text}'); ?></p>
<a href="<?php echo SimpleSAML_Utilities::selfURL(); ?>">
<?php echo $this->t('{login:login_button}'); ?>
</a>
<?php
if(!empty($this->data['links'])) {
echo '<ul class="links" style="margin-top: 2em">';
foreach($this->data['links'] AS $l) {
echo '<li><a href="' . htmlspecialchars($l['href']) . '">' . htmlspecialchars($this->t($l['text'])) . '</a></li>';
}
echo '</ul>';
}
$this->includeAtTemplateBase('includes/footer.php');
?>
......@@ -212,7 +212,52 @@ class sspmod_ldap_ConfigHelper {
return $ldap->getAttributes($dn, $this->attributes);
}
}
/**
* Search for a DN.
*
* @param string|array $attribute
* The attribute name(s) searched for. If set to NULL, values from
* configuration is used.
* @param string $value
* The attribute value searched for.
* @param bool $allowZeroHits
* Determines if the method will throw an exception if no
* hits are found. Defaults to FALSE.
* @return string
* The DN of the matching element, if found. If no element was
* found and $allowZeroHits is set to FALSE, an exception will
* be thrown; otherwise NULL will be returned.
* @throws SimpleSAML_Error_AuthSource if:
* - LDAP search encounter some problems when searching cataloge
* - Not able to connect to LDAP server
* @throws SimpleSAML_Error_UserNotFound if:
* - $allowZeroHits er TRUE and no result is found
*
*/
public function searchfordn($attribute, $value, $allowZeroHits) {
$ldap = new SimpleSAML_Auth_LDAP($this->hostname,
$this->enableTLS,
$this->debug,
$this->timeout);
if ($attribute == NULL)
$attribute = $this->searchAttributes;
?>
\ No newline at end of file
return $ldap->searchfordn($this->searchBase, $attribute,
$value, $allowZeroHits);
}
public function getAttributes($dn, $attributes = NULL) {
if ($attributes == NULL)
$attributes = $this->attributes;
$ldap = new SimpleSAML_Auth_LDAP($this->hostname,
$this->enableTLS,
$this->debug,
$this->timeout);
return $ldap->getAttributes($dn, $attributes);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment