mirror of
https://github.com/apache/httpd.git
synced 2025-08-01 07:26:57 +03:00
ClientHello properties in new SSL_CLIENTHELLO_* variables. * modules/ssl/ssl_engine_kernel.c (ssl_hook_Fixup_vars): Add SSL_CLIENTHELLO_* vars. (copy_clienthello_vars): New function. (ssl_callback_ClientHello): Call it when needed. * modules/ssl/ssl_engine_vars.c (ssl_var_lookup_ssl_clienthello): New function. (ssl_var_lookup_ssl): Call it for SSL_CLIENTHELLO_*. * modules/ssl/ssl_private.h (modssl_clienthello_vars): Add type. (SSLConnRec): Add clienthello_vars pointer. * modules/ssl/ssl_engine_config.c, modules/ssl/mod_ssl.c: Add handling of new SSLClientHelloVars directive. Submitted by: Charles Smutz <csmutz gmail.com> Github: closes #483 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1921074 13f79535-47bb-0310-9956-ffa450edef68
1324 lines
44 KiB
C
1324 lines
44 KiB
C
/* 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.
|
|
*/
|
|
|
|
/* _ _
|
|
* _ __ ___ ___ __| | ___ ___| | mod_ssl
|
|
* | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
|
|
* | | | | | | (_) | (_| | \__ \__ \ |
|
|
* |_| |_| |_|\___/ \__,_|___|___/___/_|
|
|
* |_____|
|
|
* ssl_engine_vars.c
|
|
* Variable Lookup Facility
|
|
*/
|
|
/* ``Those of you who think they
|
|
know everything are very annoying
|
|
to those of us who do.''
|
|
-- Unknown */
|
|
#include "ssl_private.h"
|
|
#include "mod_ssl.h"
|
|
#include "ap_expr.h"
|
|
|
|
#include "apr_time.h"
|
|
|
|
/* _________________________________________________________________
|
|
**
|
|
** Variable Lookup
|
|
** _________________________________________________________________
|
|
*/
|
|
|
|
static const char *ssl_var_lookup_ssl(apr_pool_t *p, const SSLConnRec *sslconn, request_rec *r, const char *var);
|
|
static const char *ssl_var_lookup_ssl_cert(apr_pool_t *p, request_rec *r, X509 *xs, const char *var);
|
|
static const char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, X509_NAME *xsname, const char *var);
|
|
static const char *ssl_var_lookup_ssl_cert_san(apr_pool_t *p, X509 *xs, const char *var);
|
|
static const char *ssl_var_lookup_ssl_cert_valid(apr_pool_t *p, ASN1_TIME *tm);
|
|
static const char *ssl_var_lookup_ssl_cert_remain(apr_pool_t *p, ASN1_TIME *tm);
|
|
static const char *ssl_var_lookup_ssl_cert_serial(apr_pool_t *p, X509 *xs);
|
|
static const char *ssl_var_lookup_ssl_cert_chain(apr_pool_t *p, STACK_OF(X509) *sk, const char *var, int pem);
|
|
static const char *ssl_var_lookup_ssl_cert_rfc4523_cea(apr_pool_t *p, SSL *ssl);
|
|
static const char *ssl_var_lookup_ssl_cert_verify(apr_pool_t *p, const SSLConnRec *sslconn);
|
|
static const char *ssl_var_lookup_ssl_cipher(apr_pool_t *p, const SSLConnRec *sslconn, const char *var);
|
|
static void ssl_var_lookup_ssl_cipher_bits(SSL *ssl, int *usekeysize, int *algkeysize);
|
|
static const char *ssl_var_lookup_ssl_handshake_rtt(apr_pool_t *p, SSL *ssl);
|
|
static const char *ssl_var_lookup_ssl_clienthello(apr_pool_t *p, const SSLConnRec *sslconn, const char *var);
|
|
static const char *ssl_var_lookup_ssl_version(const char *var);
|
|
static const char *ssl_var_lookup_ssl_compress_meth(SSL *ssl);
|
|
|
|
static const SSLConnRec *ssl_get_effective_config(conn_rec *c)
|
|
{
|
|
const SSLConnRec *sslconn = myConnConfig(c);
|
|
if (!(sslconn && sslconn->ssl) && c->master) {
|
|
/* use master connection if no SSL defined here */
|
|
sslconn = myConnConfig(c->master);
|
|
}
|
|
return sslconn;
|
|
}
|
|
|
|
static int ssl_conn_is_ssl(conn_rec *c)
|
|
{
|
|
const SSLConnRec *sslconn = ssl_get_effective_config(c);
|
|
return (sslconn && sslconn->ssl)? OK : DECLINED;
|
|
}
|
|
|
|
/* Returns certificate data, either PEM encoded if 'pem' is non-zero,
|
|
* else plain base64-encoded DER. */
|
|
static const char *ssl_var_lookup_ssl_cert_data(apr_pool_t *p, X509 *xs,
|
|
int pem)
|
|
{
|
|
BIO *bio;
|
|
|
|
if ((bio = BIO_new(BIO_s_mem())) == NULL)
|
|
return NULL;
|
|
|
|
if (pem) {
|
|
PEM_write_bio_X509(bio, xs);
|
|
}
|
|
else {
|
|
BIO *b64 = BIO_new(BIO_f_base64());
|
|
if (b64 == NULL) {
|
|
BIO_free(bio);
|
|
return NULL;
|
|
}
|
|
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
|
b64 = BIO_push(b64, bio);
|
|
i2d_X509_bio(b64, xs);
|
|
(void)BIO_flush(b64); /* ensures trailing bytes are padded */
|
|
BIO_pop(b64);
|
|
BIO_free(b64);
|
|
}
|
|
|
|
return modssl_bio_free_read(p, bio);
|
|
}
|
|
|
|
/* SSLv3 uses 36 bytes for Finishd messages, TLS1.0 12 bytes,
|
|
* So tls-unique is max 36 bytes, however with tls-server-end-point,
|
|
* the CB data is the certificate signature, so we use the maximum
|
|
* hash size known to the library (currently 64).
|
|
* */
|
|
#define TLS_CB_MAX EVP_MAX_MD_SIZE
|
|
#define TLS_UNIQUE_PREFIX "tls-unique:"
|
|
#define TLS_SERVER_END_POINT_PREFIX "tls-server-end-point:"
|
|
|
|
static apr_status_t ssl_get_tls_cb(apr_pool_t *p, conn_rec *c, const char *type,
|
|
unsigned char **buf, apr_size_t *size)
|
|
{
|
|
const SSLConnRec *sslconn = ssl_get_effective_config(c);
|
|
const char *prefix;
|
|
apr_size_t preflen;
|
|
const unsigned char *data;
|
|
unsigned char cb[TLS_CB_MAX], *retbuf;
|
|
unsigned int l = 0;
|
|
X509 *x = NULL;
|
|
|
|
if (!sslconn || !sslconn->ssl) {
|
|
return APR_EGENERAL;
|
|
}
|
|
if (strcEQ(type, "SERVER_TLS_UNIQUE")) {
|
|
l = SSL_get_peer_finished(sslconn->ssl, cb, TLS_CB_MAX);
|
|
}
|
|
else if (strcEQ(type, "CLIENT_TLS_UNIQUE")) {
|
|
l = SSL_get_finished(sslconn->ssl, cb, TLS_CB_MAX);
|
|
}
|
|
else if (strcEQ(type, "SERVER_TLS_SERVER_END_POINT")) {
|
|
x = SSL_get_certificate(sslconn->ssl);
|
|
/* Increase refcount so X509_free below works for both client
|
|
* and server cases. */
|
|
if (x) X509_up_ref(x);
|
|
}
|
|
else if (strcEQ(type, "CLIENT_TLS_SERVER_END_POINT")) {
|
|
x = SSL_get_peer_certificate(sslconn->ssl);
|
|
}
|
|
if (l > 0) {
|
|
preflen = sizeof(TLS_UNIQUE_PREFIX) -1;
|
|
prefix = TLS_UNIQUE_PREFIX;
|
|
data = cb;
|
|
}
|
|
else if (x != NULL) {
|
|
const EVP_MD *md;
|
|
|
|
#if MODSSL_USE_OPENSSL_PRE_1_1_API
|
|
md = EVP_get_digestbynid(OBJ_obj2nid(x->sig_alg->algorithm));
|
|
#else
|
|
md = EVP_get_digestbynid(X509_get_signature_nid(x));
|
|
#endif
|
|
/* Override digest as specified by RFC 5929 section 4.1. */
|
|
if (md == NULL || md == EVP_md5() || md == EVP_sha1()) {
|
|
md = EVP_sha256();
|
|
}
|
|
if (!X509_digest(x, md, cb, &l)) {
|
|
return APR_EGENERAL;
|
|
}
|
|
|
|
preflen = sizeof(TLS_SERVER_END_POINT_PREFIX) - 1;
|
|
prefix = TLS_SERVER_END_POINT_PREFIX;
|
|
data = cb;
|
|
|
|
X509_free(x);
|
|
}
|
|
else {
|
|
return APR_EGENERAL;
|
|
}
|
|
|
|
retbuf = apr_palloc(p, preflen + l);
|
|
memcpy(retbuf, prefix, preflen);
|
|
memcpy(&retbuf[preflen], data, l);
|
|
*size = preflen + l;
|
|
*buf = retbuf;
|
|
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
static const char var_interface[] = "mod_ssl/" AP_SERVER_BASEREVISION;
|
|
static char var_library_interface[] = MODSSL_LIBRARY_TEXT;
|
|
static char *var_library = NULL;
|
|
|
|
static apr_array_header_t *expr_peer_ext_list_fn(ap_expr_eval_ctx_t *ctx,
|
|
const void *dummy,
|
|
const char *arg)
|
|
{
|
|
return ssl_ext_list(ctx->p, ctx->c, 1, arg);
|
|
}
|
|
|
|
static const char *expr_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
|
|
{
|
|
const char *var = data;
|
|
const SSLConnRec *sslconn = ssl_get_effective_config(ctx->c);
|
|
|
|
return sslconn ? ssl_var_lookup_ssl(ctx->p, sslconn, ctx->r, var) : NULL;
|
|
}
|
|
|
|
static const char *expr_func_fn(ap_expr_eval_ctx_t *ctx, const void *data,
|
|
const char *arg)
|
|
{
|
|
const char *var = arg;
|
|
|
|
return var ? ssl_var_lookup(ctx->p, ctx->s, ctx->c, ctx->r, var) : NULL;
|
|
}
|
|
|
|
static int ssl_expr_lookup(ap_expr_lookup_parms *parms)
|
|
{
|
|
switch (parms->type) {
|
|
case AP_EXPR_FUNC_VAR:
|
|
/* for now, we just handle everything that starts with SSL_, but
|
|
* register our hook as APR_HOOK_LAST
|
|
* XXX: This can be optimized
|
|
*/
|
|
if (strcEQn(parms->name, "SSL_", 4)) {
|
|
*parms->func = expr_var_fn;
|
|
*parms->data = parms->name + 4;
|
|
return OK;
|
|
}
|
|
break;
|
|
case AP_EXPR_FUNC_STRING:
|
|
/* Function SSL() is implemented by us.
|
|
*/
|
|
if (strcEQ(parms->name, "SSL")) {
|
|
*parms->func = expr_func_fn;
|
|
*parms->data = NULL;
|
|
return OK;
|
|
}
|
|
break;
|
|
case AP_EXPR_FUNC_LIST:
|
|
if (strcEQ(parms->name, "PeerExtList")) {
|
|
*parms->func = expr_peer_ext_list_fn;
|
|
*parms->data = "PeerExtList";
|
|
return OK;
|
|
}
|
|
break;
|
|
}
|
|
return DECLINED;
|
|
}
|
|
|
|
|
|
void ssl_var_register(apr_pool_t *p)
|
|
{
|
|
char *cp, *cp2;
|
|
|
|
ap_hook_ssl_conn_is_ssl(ssl_conn_is_ssl, NULL, NULL, APR_HOOK_MIDDLE);
|
|
APR_REGISTER_OPTIONAL_FN(ssl_get_tls_cb);
|
|
APR_REGISTER_OPTIONAL_FN(ssl_var_lookup);
|
|
APR_REGISTER_OPTIONAL_FN(ssl_ext_list);
|
|
|
|
/* Perform once-per-process library version determination: */
|
|
var_library = apr_pstrdup(p, MODSSL_LIBRARY_DYNTEXT);
|
|
|
|
if ((cp = strchr(var_library, ' ')) != NULL) {
|
|
*cp = '/';
|
|
if ((cp2 = strchr(cp, ' ')) != NULL)
|
|
*cp2 = NUL;
|
|
}
|
|
|
|
if ((cp = strchr(var_library_interface, ' ')) != NULL) {
|
|
*cp = '/';
|
|
if ((cp2 = strchr(cp, ' ')) != NULL)
|
|
*cp2 = NUL;
|
|
}
|
|
|
|
ap_hook_expr_lookup(ssl_expr_lookup, NULL, NULL, APR_HOOK_MIDDLE);
|
|
}
|
|
|
|
/* This function must remain safe to use for a non-SSL connection. */
|
|
const char *ssl_var_lookup(apr_pool_t *p, server_rec *s,
|
|
conn_rec *c, request_rec *r,
|
|
const char *var)
|
|
{
|
|
const char *result = NULL;
|
|
apr_time_exp_t tm;
|
|
|
|
/*
|
|
* Request dependent stuff
|
|
*/
|
|
if (r != NULL) {
|
|
switch (var[0]) {
|
|
case 'H':
|
|
case 'h':
|
|
if (strcEQ(var, "HTTP_USER_AGENT"))
|
|
result = apr_table_get(r->headers_in, "User-Agent");
|
|
else if (strcEQ(var, "HTTP_REFERER"))
|
|
result = apr_table_get(r->headers_in, "Referer");
|
|
else if (strcEQ(var, "HTTP_COOKIE"))
|
|
result = apr_table_get(r->headers_in, "Cookie");
|
|
else if (strcEQ(var, "HTTP_FORWARDED"))
|
|
result = apr_table_get(r->headers_in, "Forwarded");
|
|
else if (strcEQ(var, "HTTP_HOST"))
|
|
result = apr_table_get(r->headers_in, "Host");
|
|
else if (strcEQ(var, "HTTP_PROXY_CONNECTION"))
|
|
result = apr_table_get(r->headers_in, "Proxy-Connection");
|
|
else if (strcEQ(var, "HTTP_ACCEPT"))
|
|
result = apr_table_get(r->headers_in, "Accept");
|
|
else if (strlen(var) > 5 && strcEQn(var, "HTTP:", 5))
|
|
/* all other headers from which we are still not know about */
|
|
result = apr_table_get(r->headers_in, var+5);
|
|
break;
|
|
|
|
case 'R':
|
|
case 'r':
|
|
if (strcEQ(var, "REQUEST_METHOD"))
|
|
result = r->method;
|
|
else if (strcEQ(var, "REQUEST_SCHEME"))
|
|
result = ap_http_scheme(r);
|
|
else if (strcEQ(var, "REQUEST_URI"))
|
|
result = r->uri;
|
|
else if (strcEQ(var, "REQUEST_FILENAME"))
|
|
result = r->filename;
|
|
else if (strcEQ(var, "REMOTE_ADDR"))
|
|
result = r->useragent_ip;
|
|
else if (strcEQ(var, "REMOTE_HOST"))
|
|
result = ap_get_useragent_host(r, REMOTE_NAME, NULL);
|
|
else if (strcEQ(var, "REMOTE_IDENT"))
|
|
result = ap_get_remote_logname(r);
|
|
else if (strcEQ(var, "REMOTE_USER"))
|
|
result = r->user;
|
|
break;
|
|
|
|
case 'S':
|
|
case 's':
|
|
if (strcEQn(var, "SSL", 3)) break; /* shortcut common case */
|
|
|
|
if (strcEQ(var, "SERVER_ADMIN"))
|
|
result = r->server->server_admin;
|
|
else if (strcEQ(var, "SERVER_NAME"))
|
|
result = ap_get_server_name_for_url(r);
|
|
else if (strcEQ(var, "SERVER_PORT"))
|
|
result = apr_psprintf(p, "%u", ap_get_server_port(r));
|
|
else if (strcEQ(var, "SERVER_PROTOCOL"))
|
|
result = r->protocol;
|
|
else if (strcEQ(var, "SCRIPT_FILENAME"))
|
|
result = r->filename;
|
|
break;
|
|
|
|
default:
|
|
if (strcEQ(var, "PATH_INFO"))
|
|
result = r->path_info;
|
|
else if (strcEQ(var, "QUERY_STRING"))
|
|
result = r->args;
|
|
else if (strcEQ(var, "IS_SUBREQ"))
|
|
result = (r->main != NULL ? "true" : "false");
|
|
else if (strcEQ(var, "DOCUMENT_ROOT"))
|
|
result = ap_document_root(r);
|
|
else if (strcEQ(var, "AUTH_TYPE"))
|
|
result = r->ap_auth_type;
|
|
else if (strcEQ(var, "THE_REQUEST"))
|
|
result = r->the_request;
|
|
else if (strlen(var) > 4 && strcEQn(var, "ENV:", 4)) {
|
|
result = apr_table_get(r->notes, var+4);
|
|
if (result == NULL)
|
|
result = apr_table_get(r->subprocess_env, var+4);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Connection stuff
|
|
*/
|
|
if (result == NULL && c != NULL) {
|
|
const SSLConnRec *sslconn = ssl_get_effective_config(c);
|
|
if (strlen(var) > 4 && strcEQn(var, "SSL_", 4)
|
|
&& sslconn && sslconn->ssl)
|
|
result = ssl_var_lookup_ssl(p, sslconn, r, var+4);
|
|
else if (strcEQ(var, "HTTPS")) {
|
|
if (sslconn && sslconn->ssl)
|
|
result = "on";
|
|
else
|
|
result = "off";
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Totally independent stuff
|
|
*/
|
|
if (result == NULL) {
|
|
if (strlen(var) > 12 && strcEQn(var, "SSL_VERSION_", 12))
|
|
result = ssl_var_lookup_ssl_version(var+12);
|
|
else if (strcEQ(var, "SERVER_SOFTWARE"))
|
|
result = ap_get_server_banner();
|
|
else if (strcEQ(var, "API_VERSION")) {
|
|
result = apr_itoa(p, MODULE_MAGIC_NUMBER_MAJOR);
|
|
}
|
|
else if (strcEQ(var, "TIME_YEAR")) {
|
|
apr_time_exp_lt(&tm, apr_time_now());
|
|
result = apr_psprintf(p, "%02d%02d",
|
|
(tm.tm_year / 100) + 19, tm.tm_year % 100);
|
|
}
|
|
#define MKTIMESTR(format, tmfield) \
|
|
apr_time_exp_lt(&tm, apr_time_now()); \
|
|
result = apr_psprintf(p, format, tm.tmfield);
|
|
else if (strcEQ(var, "TIME_MON")) {
|
|
MKTIMESTR("%02d", tm_mon+1)
|
|
}
|
|
else if (strcEQ(var, "TIME_DAY")) {
|
|
MKTIMESTR("%02d", tm_mday)
|
|
}
|
|
else if (strcEQ(var, "TIME_HOUR")) {
|
|
MKTIMESTR("%02d", tm_hour)
|
|
}
|
|
else if (strcEQ(var, "TIME_MIN")) {
|
|
MKTIMESTR("%02d", tm_min)
|
|
}
|
|
else if (strcEQ(var, "TIME_SEC")) {
|
|
MKTIMESTR("%02d", tm_sec)
|
|
}
|
|
else if (strcEQ(var, "TIME_WDAY")) {
|
|
MKTIMESTR("%d", tm_wday)
|
|
}
|
|
else if (strcEQ(var, "TIME")) {
|
|
apr_time_exp_lt(&tm, apr_time_now());
|
|
result = apr_psprintf(p,
|
|
"%02d%02d%02d%02d%02d%02d%02d", (tm.tm_year / 100) + 19,
|
|
(tm.tm_year % 100), tm.tm_mon+1, tm.tm_mday,
|
|
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
}
|
|
/* all other env-variables from the parent Apache process */
|
|
else if (strlen(var) > 4 && strcEQn(var, "ENV:", 4)) {
|
|
result = getenv(var+4);
|
|
}
|
|
}
|
|
|
|
return result ? result : "";
|
|
}
|
|
|
|
static const char *ssl_var_lookup_ssl(apr_pool_t *p, const SSLConnRec *sslconn,
|
|
request_rec *r, const char *var)
|
|
{
|
|
const char *result;
|
|
X509 *xs;
|
|
STACK_OF(X509) *sk;
|
|
SSL *ssl;
|
|
|
|
result = NULL;
|
|
|
|
ssl = sslconn->ssl;
|
|
if (strlen(var) > 8 && strcEQn(var, "VERSION_", 8)) {
|
|
result = ssl_var_lookup_ssl_version(var+8);
|
|
}
|
|
else if (ssl != NULL && strcEQ(var, "PROTOCOL")) {
|
|
result = SSL_get_version(ssl);
|
|
}
|
|
else if (ssl != NULL && strcEQ(var, "SESSION_ID")) {
|
|
char buf[MODSSL_SESSION_ID_STRING_LEN];
|
|
SSL_SESSION *pSession = SSL_get_session(ssl);
|
|
if (pSession) {
|
|
IDCONST unsigned char *id;
|
|
unsigned int idlen;
|
|
|
|
#ifdef OPENSSL_NO_SSL_INTERN
|
|
id = (unsigned char *)SSL_SESSION_get_id(pSession, &idlen);
|
|
#else
|
|
id = pSession->session_id;
|
|
idlen = pSession->session_id_length;
|
|
#endif
|
|
|
|
result = apr_pstrdup(p, modssl_SSL_SESSION_id2sz(id, idlen,
|
|
buf, sizeof(buf)));
|
|
}
|
|
}
|
|
else if(ssl != NULL && strcEQ(var, "SESSION_RESUMED")) {
|
|
if (SSL_session_reused(ssl) == 1)
|
|
result = "Resumed";
|
|
else
|
|
result = "Initial";
|
|
}
|
|
else if (ssl != NULL && strlen(var) >= 6 && strcEQn(var, "CIPHER", 6)) {
|
|
result = ssl_var_lookup_ssl_cipher(p, sslconn, var+6);
|
|
}
|
|
else if (ssl != NULL && strcEQ(var, "HANDSHAKE_RTT")) {
|
|
result = ssl_var_lookup_ssl_handshake_rtt(p, ssl);
|
|
}
|
|
else if (ssl != NULL && strlen(var) >= 12 && strcEQn(var, "CLIENTHELLO_", 12)) {
|
|
result = ssl_var_lookup_ssl_clienthello(p, sslconn, var+12);
|
|
}
|
|
else if (ssl != NULL && strlen(var) > 18 && strcEQn(var, "CLIENT_CERT_CHAIN_", 18)) {
|
|
sk = SSL_get_peer_cert_chain(ssl);
|
|
result = ssl_var_lookup_ssl_cert_chain(p, sk, var+18, 1);
|
|
}
|
|
else if (ssl != NULL && strlen(var) > 21 && strcEQn(var, "CLIENT_B64CERT_CHAIN_", 21)) {
|
|
sk = SSL_get_peer_cert_chain(ssl);
|
|
result = ssl_var_lookup_ssl_cert_chain(p, sk, var+21, 0);
|
|
}
|
|
else if (ssl != NULL && strcEQ(var, "CLIENT_CERT_RFC4523_CEA")) {
|
|
result = ssl_var_lookup_ssl_cert_rfc4523_cea(p, ssl);
|
|
}
|
|
else if (ssl != NULL && strcEQ(var, "CLIENT_VERIFY")) {
|
|
result = ssl_var_lookup_ssl_cert_verify(p, sslconn);
|
|
}
|
|
else if (ssl != NULL && strcEQ(var, "CLIENT_VERIFY_ERRSTR")) {
|
|
result = sslconn->verify_error;
|
|
}
|
|
else if (ssl != NULL && strlen(var) > 7 && strcEQn(var, "CLIENT_", 7)) {
|
|
if ((xs = SSL_get_peer_certificate(ssl)) != NULL) {
|
|
result = ssl_var_lookup_ssl_cert(p, r, xs, var+7);
|
|
X509_free(xs);
|
|
}
|
|
}
|
|
else if (ssl != NULL && strlen(var) > 7 && strcEQn(var, "SERVER_", 7)) {
|
|
if ((xs = SSL_get_certificate(ssl)) != NULL) {
|
|
result = ssl_var_lookup_ssl_cert(p, r, xs, var+7);
|
|
/* SSL_get_certificate is different from SSL_get_peer_certificate.
|
|
* No need to X509_free(xs).
|
|
*/
|
|
}
|
|
}
|
|
else if (ssl != NULL && strcEQ(var, "COMPRESS_METHOD")) {
|
|
result = ssl_var_lookup_ssl_compress_meth(ssl);
|
|
}
|
|
else if (ssl != NULL && strcEQ(var, "SHARED_CIPHERS")) {
|
|
char buf[HUGE_STRING_LEN * 2];
|
|
if (SSL_get_shared_ciphers(ssl, buf, sizeof(buf)))
|
|
result = apr_pstrdup(p, buf);
|
|
}
|
|
#ifdef HAVE_TLSEXT
|
|
else if (ssl != NULL && strcEQ(var, "TLS_SNI")) {
|
|
result = apr_pstrdup(p, SSL_get_servername(ssl,
|
|
TLSEXT_NAMETYPE_host_name));
|
|
}
|
|
#endif
|
|
else if (ssl != NULL && strcEQ(var, "SECURE_RENEG")) {
|
|
int flag = 0;
|
|
#ifdef SSL_get_secure_renegotiation_support
|
|
flag = SSL_get_secure_renegotiation_support(ssl);
|
|
#endif
|
|
result = flag ? "true" : "false";
|
|
}
|
|
#ifdef HAVE_SRP
|
|
else if (ssl != NULL && strcEQ(var, "SRP_USER")) {
|
|
if ((result = SSL_get_srp_username(ssl)) != NULL) {
|
|
result = apr_pstrdup(p, result);
|
|
}
|
|
}
|
|
else if (ssl != NULL && strcEQ(var, "SRP_USERINFO")) {
|
|
if ((result = SSL_get_srp_userinfo(ssl)) != NULL) {
|
|
result = apr_pstrdup(p, result);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
static const char *ssl_var_lookup_ssl_cert_dn_oneline(apr_pool_t *p, request_rec *r,
|
|
X509_NAME *xsname)
|
|
{
|
|
char *result = NULL;
|
|
SSLDirConfigRec *dc;
|
|
int legacy_format = 0;
|
|
if (r) {
|
|
dc = myDirConfig(r);
|
|
legacy_format = dc->nOptions & SSL_OPT_LEGACYDNFORMAT;
|
|
}
|
|
if (legacy_format) {
|
|
char *cp = X509_NAME_oneline(xsname, NULL, 0);
|
|
result = apr_pstrdup(p, cp);
|
|
OPENSSL_free(cp);
|
|
}
|
|
else {
|
|
BIO* bio;
|
|
unsigned long flags = XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB;
|
|
|
|
if ((bio = BIO_new(BIO_s_mem())) == NULL)
|
|
return NULL;
|
|
X509_NAME_print_ex(bio, xsname, 0, flags);
|
|
|
|
result = modssl_bio_free_read(p, bio);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static const char *ssl_var_lookup_ssl_cert(apr_pool_t *p, request_rec *r, X509 *xs,
|
|
const char *var)
|
|
{
|
|
const char *result;
|
|
X509_NAME *xsname;
|
|
int nid;
|
|
|
|
result = NULL;
|
|
|
|
if (strcEQ(var, "M_VERSION")) {
|
|
result = apr_psprintf(p, "%lu", X509_get_version(xs)+1);
|
|
}
|
|
else if (strcEQ(var, "M_SERIAL")) {
|
|
result = ssl_var_lookup_ssl_cert_serial(p, xs);
|
|
}
|
|
else if (strcEQ(var, "V_START")) {
|
|
result = ssl_var_lookup_ssl_cert_valid(p, X509_get_notBefore(xs));
|
|
}
|
|
else if (strcEQ(var, "V_END")) {
|
|
result = ssl_var_lookup_ssl_cert_valid(p, X509_get_notAfter(xs));
|
|
}
|
|
else if (strcEQ(var, "V_REMAIN")) {
|
|
result = ssl_var_lookup_ssl_cert_remain(p, X509_get_notAfter(xs));
|
|
}
|
|
else if (*var && strcEQ(var+1, "_DN")) {
|
|
if (*var == 'S')
|
|
xsname = X509_get_subject_name(xs);
|
|
else if (*var == 'I')
|
|
xsname = X509_get_issuer_name(xs);
|
|
else
|
|
return NULL;
|
|
result = ssl_var_lookup_ssl_cert_dn_oneline(p, r, xsname);
|
|
}
|
|
else if (strlen(var) > 5 && strcEQn(var+1, "_DN_", 4)) {
|
|
if (*var == 'S')
|
|
xsname = X509_get_subject_name(xs);
|
|
else if (*var == 'I')
|
|
xsname = X509_get_issuer_name(xs);
|
|
else
|
|
return NULL;
|
|
result = ssl_var_lookup_ssl_cert_dn(p, xsname, var+5);
|
|
}
|
|
else if (strlen(var) > 4 && strcEQn(var, "SAN_", 4)) {
|
|
result = ssl_var_lookup_ssl_cert_san(p, xs, var+4);
|
|
}
|
|
else if (strcEQ(var, "A_SIG")) {
|
|
#if MODSSL_USE_OPENSSL_PRE_1_1_API
|
|
nid = OBJ_obj2nid((ASN1_OBJECT *)(xs->cert_info->signature->algorithm));
|
|
#else
|
|
const ASN1_OBJECT *paobj;
|
|
X509_ALGOR_get0(&paobj, NULL, NULL, X509_get0_tbs_sigalg(xs));
|
|
nid = OBJ_obj2nid(paobj);
|
|
#endif
|
|
result = (nid == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(nid);
|
|
}
|
|
else if (strcEQ(var, "A_KEY")) {
|
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
nid = OBJ_obj2nid((ASN1_OBJECT *)(xs->cert_info->key->algor->algorithm));
|
|
#else
|
|
ASN1_OBJECT *paobj;
|
|
X509_PUBKEY_get0_param(&paobj, NULL, 0, NULL, X509_get_X509_PUBKEY(xs));
|
|
nid = OBJ_obj2nid(paobj);
|
|
#endif
|
|
result = (nid == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(nid);
|
|
}
|
|
else if (strcEQ(var, "CERT")) {
|
|
result = ssl_var_lookup_ssl_cert_data(p, xs, 1);
|
|
}
|
|
else if (strcEQ(var, "B64CERT")) {
|
|
result = ssl_var_lookup_ssl_cert_data(p, xs, 0);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* In this table, .extract is non-zero if RDNs using the NID should be
|
|
* extracted to for the SSL_{CLIENT,SERVER}_{I,S}_DN_* environment
|
|
* variables. */
|
|
static const struct {
|
|
const char *name;
|
|
int nid;
|
|
int extract;
|
|
} ssl_var_lookup_ssl_cert_dn_rec[] = {
|
|
{ "C", NID_countryName, 1 },
|
|
{ "ST", NID_stateOrProvinceName, 1 }, /* officially (RFC2156) */
|
|
{ "SP", NID_stateOrProvinceName, 0 }, /* compatibility (SSLeay) */
|
|
{ "L", NID_localityName, 1 },
|
|
{ "O", NID_organizationName, 1 },
|
|
{ "OU", NID_organizationalUnitName, 1 },
|
|
{ "CN", NID_commonName, 1 },
|
|
{ "T", NID_title, 1 },
|
|
{ "I", NID_initials, 1 },
|
|
{ "G", NID_givenName, 1 },
|
|
{ "S", NID_surname, 1 },
|
|
{ "D", NID_description, 1 },
|
|
#ifdef NID_userId
|
|
{ "UID", NID_userId, 1 },
|
|
#endif
|
|
{ "Email", NID_pkcs9_emailAddress, 1 },
|
|
{ NULL, 0, 0 }
|
|
};
|
|
|
|
static const char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, X509_NAME *xsname,
|
|
const char *var)
|
|
{
|
|
const char *ptr;
|
|
const char *result;
|
|
X509_NAME_ENTRY *xsne;
|
|
int i, j, n, idx = 0, raw = 0;
|
|
apr_size_t varlen;
|
|
|
|
ptr = ap_strrchr_c(var, '_');
|
|
if (ptr && ptr > var && strcmp(ptr + 1, "RAW") == 0) {
|
|
var = apr_pstrmemdup(p, var, ptr - var);
|
|
raw = 1;
|
|
}
|
|
|
|
/* if an _N suffix is used, find the Nth attribute of given name */
|
|
ptr = ap_strchr_c(var, '_');
|
|
if (ptr != NULL && strspn(ptr + 1, "0123456789") == strlen(ptr + 1)) {
|
|
idx = atoi(ptr + 1);
|
|
varlen = ptr - var;
|
|
} else {
|
|
varlen = strlen(var);
|
|
}
|
|
|
|
result = NULL;
|
|
|
|
for (i = 0; ssl_var_lookup_ssl_cert_dn_rec[i].name != NULL; i++) {
|
|
if (strEQn(var, ssl_var_lookup_ssl_cert_dn_rec[i].name, varlen)
|
|
&& strlen(ssl_var_lookup_ssl_cert_dn_rec[i].name) == varlen) {
|
|
for (j = 0; j < X509_NAME_entry_count(xsname); j++) {
|
|
xsne = X509_NAME_get_entry(xsname, j);
|
|
|
|
n =OBJ_obj2nid((ASN1_OBJECT *)X509_NAME_ENTRY_get_object(xsne));
|
|
|
|
if (n == ssl_var_lookup_ssl_cert_dn_rec[i].nid && idx-- == 0) {
|
|
result = modssl_X509_NAME_ENTRY_to_string(p, xsne, raw);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static const char *ssl_var_lookup_ssl_cert_san(apr_pool_t *p, X509 *xs, const char *var)
|
|
{
|
|
int type;
|
|
apr_size_t numlen;
|
|
const char *onf = NULL;
|
|
apr_array_header_t *entries;
|
|
|
|
if (strcEQn(var, "Email_", 6)) {
|
|
type = GEN_EMAIL;
|
|
var += 6;
|
|
}
|
|
else if (strcEQn(var, "DNS_", 4)) {
|
|
type = GEN_DNS;
|
|
var += 4;
|
|
}
|
|
else if (strcEQn(var, "OTHER_", 6)) {
|
|
type = GEN_OTHERNAME;
|
|
var += 6;
|
|
if (strEQn(var, "msUPN_", 6)) {
|
|
var += 6;
|
|
onf = "msUPN";
|
|
}
|
|
else if (strEQn(var, "dnsSRV_", 7)) {
|
|
var += 7;
|
|
onf = "id-on-dnsSRV";
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
else
|
|
return NULL;
|
|
|
|
/* sanity check: number must be between 1 and 4 digits */
|
|
numlen = strspn(var, "0123456789");
|
|
if ((numlen < 1) || (numlen > 4) || (numlen != strlen(var)))
|
|
return NULL;
|
|
|
|
if (modssl_X509_getSAN(p, xs, type, onf, atoi(var), &entries))
|
|
/* return the first entry from this 1-element array */
|
|
return APR_ARRAY_IDX(entries, 0, char *);
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
static const char *ssl_var_lookup_ssl_cert_valid(apr_pool_t *p, ASN1_TIME *tm)
|
|
{
|
|
BIO* bio;
|
|
|
|
if ((bio = BIO_new(BIO_s_mem())) == NULL)
|
|
return NULL;
|
|
ASN1_TIME_print(bio, tm);
|
|
|
|
return modssl_bio_free_read(p, bio);
|
|
}
|
|
|
|
#define DIGIT2NUM(x) (((x)[0] - '0') * 10 + (x)[1] - '0')
|
|
|
|
/* Return a string giving the number of days remaining until 'tm', or
|
|
* "0" if this can't be determined. */
|
|
static const char *ssl_var_lookup_ssl_cert_remain(apr_pool_t *p, ASN1_TIME *tm)
|
|
{
|
|
apr_time_t then, now = apr_time_now();
|
|
apr_time_exp_t exp = {0};
|
|
long diff;
|
|
unsigned char *dp;
|
|
|
|
/* Fail if the time isn't a valid ASN.1 TIME; RFC3280 mandates
|
|
* that the seconds digits are present even though ASN.1
|
|
* doesn't. */
|
|
if ((tm->type == V_ASN1_UTCTIME && tm->length < 11) ||
|
|
(tm->type == V_ASN1_GENERALIZEDTIME && tm->length < 13) ||
|
|
!ASN1_TIME_check(tm)) {
|
|
return "0";
|
|
}
|
|
|
|
if (tm->type == V_ASN1_UTCTIME) {
|
|
exp.tm_year = DIGIT2NUM(tm->data);
|
|
if (exp.tm_year <= 50) exp.tm_year += 100;
|
|
dp = tm->data + 2;
|
|
} else {
|
|
exp.tm_year = DIGIT2NUM(tm->data) * 100 + DIGIT2NUM(tm->data + 2) - 1900;
|
|
dp = tm->data + 4;
|
|
}
|
|
|
|
exp.tm_mon = DIGIT2NUM(dp) - 1;
|
|
exp.tm_mday = DIGIT2NUM(dp + 2) + 1;
|
|
exp.tm_hour = DIGIT2NUM(dp + 4);
|
|
exp.tm_min = DIGIT2NUM(dp + 6);
|
|
exp.tm_sec = DIGIT2NUM(dp + 8);
|
|
|
|
if (apr_time_exp_gmt_get(&then, &exp) != APR_SUCCESS) {
|
|
return "0";
|
|
}
|
|
|
|
diff = (long)((apr_time_sec(then) - apr_time_sec(now)) / (60*60*24));
|
|
|
|
return diff > 0 ? apr_ltoa(p, diff) : "0";
|
|
}
|
|
|
|
static const char *ssl_var_lookup_ssl_cert_serial(apr_pool_t *p, X509 *xs)
|
|
{
|
|
BIO *bio;
|
|
|
|
if ((bio = BIO_new(BIO_s_mem())) == NULL)
|
|
return NULL;
|
|
i2a_ASN1_INTEGER(bio, X509_get_serialNumber(xs));
|
|
|
|
return modssl_bio_free_read(p, bio);
|
|
}
|
|
|
|
static const char *ssl_var_lookup_ssl_cert_chain(apr_pool_t *p, STACK_OF(X509) *sk,
|
|
const char *var, int pem)
|
|
{
|
|
const char *result;
|
|
X509 *xs;
|
|
int n;
|
|
|
|
result = NULL;
|
|
|
|
if (strspn(var, "0123456789") == strlen(var)) {
|
|
n = atoi(var);
|
|
if (n < sk_X509_num(sk)) {
|
|
xs = sk_X509_value(sk, n);
|
|
result = ssl_var_lookup_ssl_cert_data(p, xs, pem);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static const char *ssl_var_lookup_ssl_cert_rfc4523_cea(apr_pool_t *p, SSL *ssl)
|
|
{
|
|
char *result;
|
|
char *decimal;
|
|
X509 *xs;
|
|
|
|
ASN1_INTEGER *serialNumber;
|
|
|
|
if (!(xs = SSL_get_peer_certificate(ssl))) {
|
|
return NULL;
|
|
}
|
|
|
|
result = NULL;
|
|
|
|
serialNumber = X509_get_serialNumber(xs);
|
|
if (serialNumber) {
|
|
X509_NAME *issuer = X509_get_issuer_name(xs);
|
|
if (issuer) {
|
|
BIGNUM *bn = ASN1_INTEGER_to_BN(serialNumber, NULL);
|
|
if((decimal = BN_bn2dec(bn)) == NULL) {
|
|
BN_free(bn);
|
|
X509_free(xs);
|
|
return NULL;
|
|
}
|
|
result = apr_pstrcat(p, "{ serialNumber ", decimal,
|
|
", issuer rdnSequence:\"",
|
|
modssl_X509_NAME_to_string(p, issuer, 0), "\" }", NULL);
|
|
OPENSSL_free(decimal);
|
|
BN_free(bn);
|
|
}
|
|
}
|
|
|
|
X509_free(xs);
|
|
return result;
|
|
}
|
|
|
|
static const char *ssl_var_lookup_ssl_cert_verify(apr_pool_t *p,
|
|
const SSLConnRec *sslconn)
|
|
{
|
|
char *result;
|
|
long vrc;
|
|
const char *verr;
|
|
const char *vinfo;
|
|
SSL *ssl;
|
|
X509 *xs;
|
|
|
|
result = NULL;
|
|
ssl = sslconn->ssl;
|
|
verr = sslconn->verify_error;
|
|
vinfo = sslconn->verify_info;
|
|
vrc = SSL_get_verify_result(ssl);
|
|
xs = SSL_get_peer_certificate(ssl);
|
|
|
|
if (vrc == X509_V_OK && verr == NULL && xs == NULL)
|
|
/* no client verification done at all */
|
|
result = "NONE";
|
|
else if (vrc == X509_V_OK && verr == NULL && vinfo == NULL && xs != NULL)
|
|
/* client verification done successful */
|
|
result = "SUCCESS";
|
|
else if (vrc == X509_V_OK && vinfo != NULL && strEQ(vinfo, "GENEROUS"))
|
|
/* client verification done in generous way */
|
|
result = "GENEROUS";
|
|
else
|
|
/* client verification failed */
|
|
result = apr_psprintf(p, "FAILED:%s",
|
|
verr ? verr : X509_verify_cert_error_string(vrc));
|
|
|
|
if (xs)
|
|
X509_free(xs);
|
|
return result;
|
|
}
|
|
|
|
static const char *ssl_var_lookup_ssl_cipher(apr_pool_t *p,
|
|
const SSLConnRec *sslconn,
|
|
const char *var)
|
|
{
|
|
const char *result;
|
|
int usekeysize, algkeysize;
|
|
SSL *ssl;
|
|
|
|
result = NULL;
|
|
|
|
ssl = sslconn->ssl;
|
|
ssl_var_lookup_ssl_cipher_bits(ssl, &usekeysize, &algkeysize);
|
|
|
|
if (ssl && strEQ(var, "")) {
|
|
MODSSL_SSL_CIPHER_CONST SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
|
|
|
|
if (cipher) {
|
|
result = apr_pstrdup(p, SSL_CIPHER_get_name(cipher));
|
|
}
|
|
}
|
|
else if (strcEQ(var, "_EXPORT"))
|
|
result = (usekeysize < 56 ? "true" : "false");
|
|
else if (strcEQ(var, "_USEKEYSIZE")) {
|
|
result = apr_itoa(p, usekeysize);
|
|
}
|
|
else if (strcEQ(var, "_ALGKEYSIZE")) {
|
|
result = apr_itoa(p, algkeysize);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void ssl_var_lookup_ssl_cipher_bits(SSL *ssl, int *usekeysize, int *algkeysize)
|
|
{
|
|
MODSSL_SSL_CIPHER_CONST SSL_CIPHER *cipher;
|
|
|
|
*usekeysize = 0;
|
|
*algkeysize = 0;
|
|
if (ssl != NULL)
|
|
if ((cipher = SSL_get_current_cipher(ssl)) != NULL)
|
|
*usekeysize = SSL_CIPHER_get_bits(cipher, algkeysize);
|
|
return;
|
|
}
|
|
|
|
static const char *ssl_var_lookup_ssl_handshake_rtt(apr_pool_t *p, SSL *ssl)
|
|
{
|
|
#if OPENSSL_VERSION_NUMBER >= 0x30200000L
|
|
apr_uint64_t rtt;
|
|
if (SSL_get_handshake_rtt(ssl, &rtt) > 0)
|
|
return apr_psprintf(p, "%" APR_UINT64_T_FMT, rtt);
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
static const char *ssl_var_lookup_ssl_clienthello(apr_pool_t *p, const SSLConnRec *sslconn, const char *var)
|
|
{
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
|
|
char *value;
|
|
modssl_clienthello_vars *clienthello_vars;
|
|
apr_size_t i;
|
|
|
|
clienthello_vars = sslconn->clienthello_vars;
|
|
|
|
if (!clienthello_vars)
|
|
return NULL;
|
|
|
|
if (strEQ(var, "VERSION")) {
|
|
return apr_psprintf(p, "%04x", (uint16_t) clienthello_vars->version);
|
|
}
|
|
else if (strEQ(var, "CIPHERS") && (clienthello_vars->ciphers_len > 0)) {
|
|
value = apr_palloc(p, clienthello_vars->ciphers_len * 2 + 1);
|
|
ap_bin2hex(clienthello_vars->ciphers_data, clienthello_vars->ciphers_len, value);
|
|
return value;
|
|
}
|
|
else if (strEQ(var, "EXTENSIONS") && (clienthello_vars->extids_len > 0)) {
|
|
value = apr_palloc(p, clienthello_vars->extids_len * 4 + 1);
|
|
for (i = 0; i < clienthello_vars->extids_len; i++) {
|
|
apr_snprintf(value + i * 4, 5, "%04x", (uint16_t) clienthello_vars->extids_data[i]);
|
|
}
|
|
return value;
|
|
}
|
|
else if (strEQ(var, "GROUPS") && (clienthello_vars->ecgroups_len > 2)) {
|
|
value = apr_palloc(p, clienthello_vars->ecgroups_len * 2 + 1 - 2);
|
|
ap_bin2hex(clienthello_vars->ecgroups_data + 2, clienthello_vars->ecgroups_len - 2, value);
|
|
return value;
|
|
}
|
|
else if (strEQ(var, "EC_FORMATS") && (clienthello_vars->ecformats_len > 1)) {
|
|
value = apr_palloc(p, clienthello_vars->ecformats_len * 2 + 1 - 1);
|
|
ap_bin2hex(clienthello_vars->ecformats_data + 1, clienthello_vars->ecformats_len - 1, value);
|
|
return value;
|
|
}
|
|
else if (strEQ(var, "SIG_ALGOS") && (clienthello_vars->sigalgos_len > 2)) {
|
|
value = apr_palloc(p, clienthello_vars->sigalgos_len * 2 + 1 - 2);
|
|
ap_bin2hex(clienthello_vars->sigalgos_data + 2, clienthello_vars->sigalgos_len - 2, value);
|
|
return value;
|
|
}
|
|
else if (strEQ(var, "ALPN") && (clienthello_vars->alpn_len > 2)) {
|
|
value = apr_palloc(p, clienthello_vars->alpn_len * 2 + 1 - 2);
|
|
ap_bin2hex(clienthello_vars->alpn_data + 2, clienthello_vars->alpn_len - 2, value);
|
|
return value;
|
|
}
|
|
else if (strEQ(var, "VERSIONS") && (clienthello_vars->versions_len > 1)) {
|
|
value = apr_palloc(p, clienthello_vars->versions_len * 2 + 1 - 1);
|
|
ap_bin2hex(clienthello_vars->versions_data + 1, clienthello_vars->versions_len - 1, value);
|
|
return value;
|
|
}
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
static const char *ssl_var_lookup_ssl_version(const char *var)
|
|
{
|
|
if (strEQ(var, "INTERFACE")) {
|
|
return var_interface;
|
|
}
|
|
else if (strEQ(var, "LIBRARY_INTERFACE")) {
|
|
return var_library_interface;
|
|
}
|
|
else if (strEQ(var, "LIBRARY")) {
|
|
return var_library;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Add each RDN in 'xn' to the table 't' where the NID is present in
|
|
* 'nids', using key prefix 'pfx'. */
|
|
static void extract_dn(apr_table_t *t, apr_hash_t *nids, const char *pfx,
|
|
X509_NAME *xn, apr_pool_t *p)
|
|
{
|
|
X509_NAME_ENTRY *xsne;
|
|
apr_hash_t *count;
|
|
int i, nid;
|
|
|
|
/* Hash of (int) NID -> (int *) counter to count each time an RDN
|
|
* with the given NID has been seen. */
|
|
count = apr_hash_make(p);
|
|
|
|
/* For each RDN... */
|
|
for (i = 0; i < X509_NAME_entry_count(xn); i++) {
|
|
const char *tag;
|
|
xsne = X509_NAME_get_entry(xn, i);
|
|
|
|
/* Retrieve the nid, and check whether this is one of the nids
|
|
* which are to be extracted. */
|
|
nid = OBJ_obj2nid((ASN1_OBJECT *)X509_NAME_ENTRY_get_object(xsne));
|
|
|
|
tag = apr_hash_get(nids, &nid, sizeof nid);
|
|
if (tag) {
|
|
const char *key;
|
|
int *dup;
|
|
char *value;
|
|
|
|
/* Check whether a variable with this nid was already
|
|
* been used; if so, use the foo_N=bar syntax. */
|
|
dup = apr_hash_get(count, &nid, sizeof nid);
|
|
if (dup) {
|
|
key = apr_psprintf(p, "%s%s_%d", pfx, tag, ++(*dup));
|
|
}
|
|
else {
|
|
/* Otherwise, use the plain foo=bar syntax. */
|
|
dup = apr_pcalloc(p, sizeof *dup);
|
|
apr_hash_set(count, &nid, sizeof nid, dup);
|
|
key = apr_pstrcat(p, pfx, tag, NULL);
|
|
}
|
|
value = modssl_X509_NAME_ENTRY_to_string(p, xsne, 0);
|
|
apr_table_setn(t, key, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
void modssl_var_extract_dns(apr_table_t *t, SSL *ssl, apr_pool_t *p)
|
|
{
|
|
apr_hash_t *nids;
|
|
unsigned n;
|
|
X509 *xs;
|
|
|
|
/* Build up a hash table of (int *)NID->(char *)short-name for all
|
|
* the tags which are to be extracted: */
|
|
nids = apr_hash_make(p);
|
|
for (n = 0; ssl_var_lookup_ssl_cert_dn_rec[n].name; n++) {
|
|
if (ssl_var_lookup_ssl_cert_dn_rec[n].extract) {
|
|
apr_hash_set(nids, &ssl_var_lookup_ssl_cert_dn_rec[n].nid,
|
|
sizeof(ssl_var_lookup_ssl_cert_dn_rec[0].nid),
|
|
ssl_var_lookup_ssl_cert_dn_rec[n].name);
|
|
}
|
|
}
|
|
|
|
/* Extract the server cert DNS -- note that the refcount does NOT
|
|
* increase: */
|
|
xs = SSL_get_certificate(ssl);
|
|
if (xs) {
|
|
extract_dn(t, nids, "SSL_SERVER_S_DN_", X509_get_subject_name(xs), p);
|
|
extract_dn(t, nids, "SSL_SERVER_I_DN_", X509_get_issuer_name(xs), p);
|
|
}
|
|
|
|
/* Extract the client cert DNs -- note that the refcount DOES
|
|
* increase: */
|
|
xs = SSL_get_peer_certificate(ssl);
|
|
if (xs) {
|
|
extract_dn(t, nids, "SSL_CLIENT_S_DN_", X509_get_subject_name(xs), p);
|
|
extract_dn(t, nids, "SSL_CLIENT_I_DN_", X509_get_issuer_name(xs), p);
|
|
X509_free(xs);
|
|
}
|
|
}
|
|
|
|
static void extract_san_array(apr_table_t *t, const char *pfx,
|
|
apr_array_header_t *entries, apr_pool_t *p)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < entries->nelts; i++) {
|
|
const char *key = apr_psprintf(p, "%s_%d", pfx, i);
|
|
apr_table_setn(t, key, APR_ARRAY_IDX(entries, i, const char *));
|
|
}
|
|
}
|
|
|
|
void modssl_var_extract_san_entries(apr_table_t *t, SSL *ssl, apr_pool_t *p)
|
|
{
|
|
X509 *xs;
|
|
apr_array_header_t *entries;
|
|
|
|
/* subjectAltName entries of the server certificate */
|
|
xs = SSL_get_certificate(ssl);
|
|
if (xs) {
|
|
if (modssl_X509_getSAN(p, xs, GEN_EMAIL, NULL, -1, &entries)) {
|
|
extract_san_array(t, "SSL_SERVER_SAN_Email", entries, p);
|
|
}
|
|
if (modssl_X509_getSAN(p, xs, GEN_DNS, NULL, -1, &entries)) {
|
|
extract_san_array(t, "SSL_SERVER_SAN_DNS", entries, p);
|
|
}
|
|
if (modssl_X509_getSAN(p, xs, GEN_OTHERNAME, "id-on-dnsSRV", -1,
|
|
&entries)) {
|
|
extract_san_array(t, "SSL_SERVER_SAN_OTHER_dnsSRV", entries, p);
|
|
}
|
|
/* no need to free xs (refcount does not increase) */
|
|
}
|
|
|
|
/* subjectAltName entries of the client certificate */
|
|
xs = SSL_get_peer_certificate(ssl);
|
|
if (xs) {
|
|
if (modssl_X509_getSAN(p, xs, GEN_EMAIL, NULL, -1, &entries)) {
|
|
extract_san_array(t, "SSL_CLIENT_SAN_Email", entries, p);
|
|
}
|
|
if (modssl_X509_getSAN(p, xs, GEN_DNS, NULL, -1, &entries)) {
|
|
extract_san_array(t, "SSL_CLIENT_SAN_DNS", entries, p);
|
|
}
|
|
if (modssl_X509_getSAN(p, xs, GEN_OTHERNAME, "msUPN", -1, &entries)) {
|
|
extract_san_array(t, "SSL_CLIENT_SAN_OTHER_msUPN", entries, p);
|
|
}
|
|
X509_free(xs);
|
|
}
|
|
}
|
|
|
|
/* For an extension type which OpenSSL does not recognize, attempt to
|
|
* parse the extension type as a primitive string. This will fail for
|
|
* any structured extension type per the docs. Returns non-zero on
|
|
* success and writes the string to the given bio. */
|
|
static int dump_extn_value(BIO *bio, ASN1_OCTET_STRING *str)
|
|
{
|
|
const unsigned char *pp = str->data;
|
|
ASN1_STRING *ret = ASN1_STRING_new();
|
|
int rv = 0;
|
|
|
|
if(!ret) {
|
|
return rv;
|
|
}
|
|
|
|
/* This allows UTF8String, IA5String, VisibleString, or BMPString;
|
|
* conversion to UTF-8 is forced. */
|
|
if (d2i_DISPLAYTEXT(&ret, &pp, str->length)) {
|
|
ASN1_STRING_print_ex(bio, ret, ASN1_STRFLGS_UTF8_CONVERT);
|
|
rv = 1;
|
|
}
|
|
|
|
ASN1_STRING_free(ret);
|
|
return rv;
|
|
}
|
|
|
|
apr_array_header_t *ssl_ext_list(apr_pool_t *p, conn_rec *c, int peer,
|
|
const char *extension)
|
|
{
|
|
const SSLConnRec *sslconn = ssl_get_effective_config(c);
|
|
SSL *ssl = NULL;
|
|
apr_array_header_t *array = NULL;
|
|
X509 *xs = NULL;
|
|
ASN1_OBJECT *oid = NULL;
|
|
int count = 0, j;
|
|
|
|
if (!sslconn || !sslconn->ssl || !extension) {
|
|
return NULL;
|
|
}
|
|
ssl = sslconn->ssl;
|
|
|
|
/* We accept the "extension" string to be converted as
|
|
* a long name (nsComment), short name (DN) or
|
|
* numeric OID (1.2.3.4).
|
|
*/
|
|
oid = OBJ_txt2obj(extension, 0);
|
|
if (!oid) {
|
|
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01970)
|
|
"could not parse OID '%s'", extension);
|
|
ERR_clear_error();
|
|
return NULL;
|
|
}
|
|
|
|
xs = peer ? SSL_get_peer_certificate(ssl) : SSL_get_certificate(ssl);
|
|
if (xs == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
count = X509_get_ext_count(xs);
|
|
/* Create an array large enough to accommodate every extension. This is
|
|
* likely overkill, but safe.
|
|
*/
|
|
array = apr_array_make(p, count, sizeof(char *));
|
|
for (j = 0; j < count; j++) {
|
|
X509_EXTENSION *ext = X509_get_ext(xs, j);
|
|
|
|
if (OBJ_cmp(X509_EXTENSION_get_object(ext), oid) == 0) {
|
|
BIO *bio = BIO_new(BIO_s_mem());
|
|
if(bio == NULL) {
|
|
X509_free(xs);
|
|
ASN1_OBJECT_free(oid);
|
|
return NULL;
|
|
}
|
|
|
|
/* We want to obtain a string representation of the extensions
|
|
* value and add it to the array we're building.
|
|
* X509V3_EXT_print() doesn't know about all the possible
|
|
* data types, but the value is stored as an ASN1_OCTET_STRING
|
|
* allowing us a fallback in case of X509V3_EXT_print
|
|
* not knowing how to handle the data.
|
|
*/
|
|
if (X509V3_EXT_print(bio, ext, 0, 0) == 1 ||
|
|
dump_extn_value(bio, X509_EXTENSION_get_data(ext)) == 1) {
|
|
BUF_MEM *buf;
|
|
char **ptr = apr_array_push(array);
|
|
BIO_get_mem_ptr(bio, &buf);
|
|
*ptr = apr_pstrmemdup(p, buf->data, buf->length);
|
|
} else {
|
|
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01971)
|
|
"Found an extension '%s', but failed to "
|
|
"create a string from it", extension);
|
|
}
|
|
BIO_vfree(bio);
|
|
}
|
|
}
|
|
|
|
if (array->nelts == 0)
|
|
array = NULL;
|
|
|
|
if (peer) {
|
|
/* only SSL_get_peer_certificate raises the refcount */
|
|
X509_free(xs);
|
|
}
|
|
|
|
ASN1_OBJECT_free(oid);
|
|
ERR_clear_error();
|
|
return array;
|
|
}
|
|
|
|
static const char *ssl_var_lookup_ssl_compress_meth(SSL *ssl)
|
|
{
|
|
const char *result = "NULL";
|
|
#ifndef OPENSSL_NO_COMP
|
|
SSL_SESSION *pSession = SSL_get_session(ssl);
|
|
|
|
if (pSession) {
|
|
#ifdef OPENSSL_NO_SSL_INTERN
|
|
switch (SSL_SESSION_get_compress_id(pSession)) {
|
|
#else
|
|
switch (pSession->compress_meth) {
|
|
#endif
|
|
case 0:
|
|
/* default "NULL" already set */
|
|
break;
|
|
|
|
/* Defined by RFC 3749, deflate is coded by "1" */
|
|
case 1:
|
|
result = "DEFLATE";
|
|
break;
|
|
|
|
/* IANA assigned compression number for LZS */
|
|
case 0x40:
|
|
result = "LZS";
|
|
break;
|
|
|
|
default:
|
|
result = "UNKNOWN";
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
return result;
|
|
}
|
|
|