--- modules/aaa/mod_authnz_ldap.c (revision 931264) +++ modules/aaa/mod_authnz_ldap.c (working copy) @@ -398,8 +398,17 @@ authn_ldap_build_filter(filtbuf, r, user, NULL, sec); /* do the user search */ - result = util_ldap_cache_checkuserid(r, ldc, sec->url, sec->basedn, sec->scope, - sec->attributes, filtbuf, password, &dn, &vals); + if (r->ap_auth_type && !strcasecmp(r->ap_auth_type, "Basic")) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0 ,r, + "[%" APR_PID_T_FMT "] auth_ldap authenticate: using filter %s", getpid(), filtbuf); + result = util_ldap_cache_checkuserid(r, ldc, sec->url, sec->basedn, sec->scope, + sec->attributes, filtbuf, password, &dn, &vals); + } else if (r->ap_auth_type && !strcasecmp(r->ap_auth_type, "Certificate")) { + result = (sec->user_is_dn) ? util_ldap_cache_getuserdn(r, ldc, sec->url, user, LDAP_SCOPE_BASE, + sec->attributes, sec->filter, &dn, &vals) : + util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn, sec->scope, + sec->attributes, filtbuf, &dn, &vals); + } util_ldap_connection_close(ldc); /* sanity check - if server is down, retry it up to 5 times */ @@ -487,6 +496,21 @@ return AUTH_GRANTED; } +static authn_status authn_ldap_check_certificate(request_rec *r,const char *username) +{ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "[%" APR_PID_T_FMT "] auth_ldap check_certificate: user %s", + getpid(), username); + /* short-circuit this error early so that we don't get a misleading error later */ + if (username == NULL || username == "") { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "[%" APR_PID_T_FMT "] auth_ldap authenticate: no certificate specified", getpid()); + return AUTH_GENERAL_ERROR; + } + + /* To borrow check_password we have to supply a placeholder password */ + authn_status result = authn_ldap_check_password(r, username, "password"); + return result; +} /* * Authorisation Phase * ------------------- @@ -613,8 +637,11 @@ authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec); /* Search for the user DN */ - result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn, - sec->scope, sec->attributes, filtbuf, &dn, &vals); + result = (sec->user_is_dn && r->ap_auth_type && !strcasecmp(r->ap_auth_type, "Certificate")) ? + util_ldap_cache_getuserdn(r, ldc, sec->url, r->user, LDAP_SCOPE_BASE, + sec->attributes, sec->filter, &dn, &vals) : + util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn, sec->scope, + sec->attributes, filtbuf, &dn, &vals); /* Search failed, log error and return failure */ if(result != LDAP_SUCCESS) { @@ -818,8 +845,11 @@ authn_ldap_build_filter(filtbuf, r, req->user, t, sec); /* Search for the user DN */ - result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn, - sec->scope, sec->attributes, filtbuf, &dn, &vals); + result = (sec->user_is_dn && r->ap_auth_type && !strcasecmp(r->ap_auth_type, "Certificate")) ? + util_ldap_cache_getuserdn(r, ldc, sec->url, req->dn, LDAP_SCOPE_BASE, + sec->attributes, t, &dn, &vals) : + util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn, sec->scope, + sec->attributes, filtbuf, &dn, &vals); /* Make sure that the filtered search returned the correct user dn */ if (result == LDAP_SUCCESS) { @@ -1234,11 +1264,12 @@ static void register_hooks(apr_pool_t *p) { static const char * const aszPost[]={ "mod_authz_user.c", NULL }; + static const char * const aszPre[]={ "mod_ssl.c", NULL }; ap_register_provider(p, AUTHN_PROVIDER_GROUP, "ldap", "0", &authn_ldap_provider); ap_hook_post_config(authnz_ldap_post_config,NULL,NULL,APR_HOOK_MIDDLE); - ap_hook_auth_checker(authz_ldap_check_user_access, NULL, aszPost, APR_HOOK_MIDDLE); + ap_hook_auth_checker(authz_ldap_check_user_access, aszPre, aszPost, APR_HOOK_MIDDLE); ap_hook_optional_fn_retrieve(ImportULDAPOptFn,NULL,NULL,APR_HOOK_MIDDLE); } --- modules/aaa/config.m4 (revision 931264) +++ modules/aaa/config.m4 (working copy) @@ -54,5 +54,6 @@ enable_auth_digest="no" fi ]) +APACHE_MODULE(auth_cert, X.509 certificate authentication, , , most) APACHE_MODPATH_FINISH --- modules/aaa/mod_auth.h (revision 931264) +++ modules/aaa/mod_auth.h (working copy) @@ -62,6 +62,11 @@ */ authn_status (*get_realm_hash)(request_rec *r, const char *user, const char *realm, char **rethash); + + /* Given an X.509 certificate, expected to return AUTH_GRANTED + * if the provider authenticates the certificate */ + authn_status (*check_certificate)(request_rec *r, const char *certificate); + } authn_provider; /* A linked-list of authn providers. */ --- modules/aaa/mod_auth_cert.c (revision 0) +++ modules/aaa/mod_auth_cert.c (revision 0) @@ -0,0 +1,292 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr_strings.h" +#define APR_WANT_STRFUNC /* for strcasecmp */ +#include "apr_want.h" + +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_protocol.h" +#include "http_request.h" +#include "ap_provider.h" + +#include "mod_auth.h" +#include "mod_ssl.h" + +typedef struct { + authn_provider_list *providers; + char *dir; + int authoritative; +} auth_cert_config_rec; + +static void *create_auth_cert_dir_config(apr_pool_t *p, char *d) +{ + auth_cert_config_rec *conf = apr_pcalloc(p, sizeof(*conf)); + + conf->dir = d; + /* Any failures are fatal. */ + conf->authoritative = 1; + + return conf; +} + +static const char *add_authn_provider(cmd_parms *cmd, void *config, + const char *arg) +{ + auth_cert_config_rec *conf = (auth_cert_config_rec*)config; + authn_provider_list *newp; + + newp = apr_pcalloc(cmd->pool, sizeof(authn_provider_list)); + newp->provider_name = apr_pstrdup(cmd->pool, arg); + + /* lookup and cache the actual provider now */ + newp->provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP, + newp->provider_name, "0"); + + if (newp->provider == NULL) { + /* by the time they use it, the provider should be loaded and + registered with us. */ + return apr_psprintf(cmd->pool, + "Unknown Authn provider: %s", + newp->provider_name); + } + + if (!newp->provider->check_certificate) { + /* if it doesn't provide the appropriate function, reject it */ + return apr_psprintf(cmd->pool, + "The '%s' Authn provider doesn't support " + "Certificate Authentication", newp->provider_name); + } + + /* Add it to the list now. */ + if (!conf->providers) { + conf->providers = newp; + } + else { + authn_provider_list *last = conf->providers; + + while (last->next) { + last = last->next; + } + last->next = newp; + } + + return NULL; +} + +static const command_rec auth_cert_cmds[] = +{ + AP_INIT_ITERATE("AuthCertificateProvider", add_authn_provider, NULL, OR_AUTHCFG, + "specify the auth providers for a directory or location"), + AP_INIT_FLAG("AuthCertificateAuthoritative", ap_set_flag_slot, + (void *)APR_OFFSETOF(auth_cert_config_rec, authoritative), + OR_AUTHCFG, + "Set to 'Off' to allow access control to be passed along to " + "lower modules if the UserID is not known to this module"), + {NULL} +}; + +static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *ssl_svl_func = NULL; + +module AP_MODULE_DECLARE_DATA auth_cert_module; + +/* These functions return 0 if client is OK, and proper error status + * if not... either HTTP_UNAUTHORIZED, if we made a check, and it failed, or + * HTTP_INTERNAL_SERVER_ERROR, if things are so totally confused that we + * couldn't figure out how to tell if the client is authorized or not. + * + * If they return DECLINED, and all other modules also decline, that's + * treated by the server core as a configuration error, logged and + * reported as such. + */ + +/* Determine user ID, and check if it really is that user, for HTTP + */ +static int authenticate_cert_user(request_rec *r) +{ + auth_cert_config_rec *conf = ap_get_module_config(r->per_dir_config, + &auth_cert_module); + const char *username, *current_auth; + const char *verstatus; + authn_status auth_result; + authn_provider_list *current_provider; + + /* Are we configured to be Certificate auth? */ + current_auth = ap_auth_type(r); + if (!current_auth || strcasecmp(current_auth, "Certificate")) { + return DECLINED; + } + + /* We need an authentication realm. */ + if (!ap_auth_name(r)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, + 0, r, "need AuthName: %s", r->uri); + return HTTP_INTERNAL_SERVER_ERROR; + } + + r->ap_auth_type = (char*)current_auth; + + verstatus = ssl_svl_func(r->pool,r->server,r->connection,r,"SSL_CLIENT_VERIFY"); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "Client certificate status is: %s", verstatus); + + switch (verstatus[0]) { + case 'G': // ENEROUS + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + "Client certificate is not from a trusted CA"); + break; + case 'S': // UCCESS + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "Client certificate accepted."); + break; + case 'N': // ONE + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Client certificate not provided"); + return HTTP_INTERNAL_SERVER_ERROR; + case 'F': // AILED + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Authentication " + "aborted; certificate verification %s", + verstatus); + return HTTP_UNAUTHORIZED; + default: // Only possible if another return is defined by mod_ssl.c + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Unexpected return " + "code from certificate verification %s", + verstatus); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* + * At this point we know we have a certificate. + */ + + username = ssl_svl_func(r->pool,r->server,r->connection,r,"SSL_CLIENT_S_DN"); + r->user = (char *) username; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "user %s: attempting certificate authentication for \"%s\": ", + username, r->uri); + + current_provider = conf->providers; + do { + const authn_provider *provider; + + /* For now, if a provider isn't set, we'll be nice and use the file + * provider. + */ + if (!current_provider) { + provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP, + AUTHN_DEFAULT_PROVIDER, "0"); + + if (!provider || !provider->check_certificate) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "No Authn provider configured"); + auth_result = AUTH_GENERAL_ERROR; + break; + } + apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, AUTHN_DEFAULT_PROVIDER); + } + else { + provider = current_provider->provider; + apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, current_provider->provider_name); + } + + auth_result = provider->check_certificate(r, r->user); + + apr_table_unset(r->notes, AUTHN_PROVIDER_NAME_NOTE); + + /* Something occured. Stop checking. */ + if (auth_result != AUTH_USER_NOT_FOUND) { + break; + } + + /* If we're not really configured for providers, stop now. */ + if (!conf->providers) { + break; + } + + current_provider = current_provider->next; + } while (current_provider); + + if (auth_result != AUTH_GRANTED) { + int return_code; + + /* If we're not authoritative, then any error is ignored. */ + if (!(conf->authoritative) && auth_result != AUTH_DENIED) { + return DECLINED; + } + + switch (auth_result) { + case AUTH_DENIED: + /* Currently we are not passing a user name with the certificate. + * This may change eventually, in which case we could see a denial + * where the requested user is found, but there is no corresponding + * certificate in its attributes. Until then AUTH_DENIED + * and AUTH_USER_NOT_FOUND are really identical results. + */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "user %s: authentication failure for \"%s\": " + "Certificate Mismatch", + r->user, r->uri); + return_code = HTTP_UNAUTHORIZED; + break; + case AUTH_USER_NOT_FOUND: + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "user %s not found: %s", r->user, r->uri); + return_code = HTTP_UNAUTHORIZED; + break; + case AUTH_GENERAL_ERROR: + default: + /* We'll assume that the module has already said what its error + * was in the logs. + */ + return_code = HTTP_INTERNAL_SERVER_ERROR; + break; + } + + return return_code; + } + + return OK; +} + +static int auth_cert_post_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + ssl_svl_func = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); + return OK; +} + + +static void register_hooks(apr_pool_t *p) +{ + static const char * const aszPre[]={ "mod_ssl.c",NULL }; + ap_hook_check_user_id(authenticate_cert_user, aszPre, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(auth_cert_post_config, NULL, NULL, APR_HOOK_MIDDLE); +} + +module AP_MODULE_DECLARE_DATA auth_cert_module = +{ + STANDARD20_MODULE_STUFF, + create_auth_cert_dir_config, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + auth_cert_cmds, /* command apr_table_t */ + register_hooks /* register hooks */ +}; --- modules/ssl/ssl_engine_vars.c (revision 931264) +++ modules/ssl/ssl_engine_vars.c (working copy) @@ -367,10 +367,20 @@ } else if (strcEQ(var, "S_DN")) { xsname = X509_get_subject_name(xs); - cp = X509_NAME_oneline(xsname, NULL, 0); - result = apr_pstrdup(p, cp); - modssl_free(cp); - resdup = FALSE; + BIO *bio; + int n; + + if ((bio = BIO_new(BIO_s_mem())) == NULL) { + result = NULL; + } else { + X509_NAME_print_ex(bio, xsname, 0, XN_FLAG_RFC2253); + n = BIO_pending(bio); + result = apr_pcalloc(p, n+1); + n = BIO_read(bio, result, n); + result[n] = NUL; + BIO_free(bio); + resdup = FALSE; + } } else if (strlen(var) > 5 && strcEQn(var, "S_DN_", 5)) { xsname = X509_get_subject_name(xs);