| /* 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, const 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, const ASN1_TIME *tm); | |
| static const char *ssl_var_lookup_ssl_cert_remain(apr_pool_t *p, const 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 : ""; | |
| } | |
| #ifdef HAVE_OPENSSL_ECH | |
| /* Extract ECH status variable from SSL object 'ssl' */ | |
| static const char *ssl_var_lookup_ech_status(apr_pool_t *p, const char *var, | |
| SSL *ssl) | |
| { | |
| char *inner_sni = NULL, *outer_sni = NULL; | |
| int echrv; | |
| const char *result = NULL; | |
| if (ssl == NULL) | |
| return result; | |
| echrv = SSL_ech_get1_status(ssl, &inner_sni, &outer_sni); | |
| if (strcEQ(var, "STATUS")) { | |
| switch (echrv) { | |
| case SSL_ECH_STATUS_NOT_TRIED: | |
| result = "not attempted"; | |
| break; | |
| case SSL_ECH_STATUS_FAILED: | |
| result = "tried but failed"; | |
| break; | |
| case SSL_ECH_STATUS_BAD_NAME: | |
| result = "ECH worked but bad name"; | |
| break; | |
| case SSL_ECH_STATUS_SUCCESS: | |
| result = "success"; | |
| break; | |
| default: | |
| result = "error getting ECH status"; | |
| } | |
| } | |
| else if (echrv == SSL_ECH_STATUS_SUCCESS) { | |
| if (strcEQ(var, "INNER_SNI")) { | |
| result = apr_pstrdup(p, inner_sni); | |
| } | |
| if (strcEQ(var, "OUTER_SNI")) { | |
| result = apr_pstrdup(p, outer_sni); | |
| } | |
| } | |
| OPENSSL_free(inner_sni); | |
| OPENSSL_free(outer_sni); | |
| return result; | |
| } | |
| #endif | |
| 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 | |
| #ifdef HAVE_OPENSSL_ECH | |
| else if (ssl != NULL && strcEQn(var, "ECH_", 4)) { | |
| result = ssl_var_lookup_ech_status(p, var+4, ssl); | |
| } | |
| #endif | |
| return result; | |
| } | |
| static const char *ssl_var_lookup_ssl_cert_dn_oneline(apr_pool_t *p, request_rec *r, | |
| const 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; | |
| const 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_get0_notBefore(xs)); | |
| } | |
| else if (strcEQ(var, "V_END")) { | |
| result = ssl_var_lookup_ssl_cert_valid(p, X509_get0_notAfter(xs)); | |
| } | |
| else if (strcEQ(var, "V_REMAIN")) { | |
| result = ssl_var_lookup_ssl_cert_remain(p, X509_get0_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 }, | |
| { "SerialNumber", NID_serialNumber, 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, const X509_NAME *xsname, | |
| const char *var) | |
| { | |
| const char *ptr; | |
| const char *result; | |
| const 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(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, const 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); | |
| } | |
| /* Evaluates to true if asn1 isn't a valid ASN.1 TIME; RFC3280 | |
| * mandates that the seconds digits are present even though ASN.1 | |
| * doesn't. */ | |
| #define INVALID_ASN1_TIME(asn1) ( \ | |
| ((asn1)->type == V_ASN1_UTCTIME && (asn1)->length < 11) \ | |
| || ((asn1)->type == V_ASN1_GENERALIZEDTIME && (asn1)->length < 13) \ | |
| || ASN1_TIME_check(asn1) != 1) | |
| /* 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, const ASN1_TIME *tm) | |
| { | |
| #if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) | |
| int diff; | |
| if (ASN1_TIME_check(tm) != 1 || ASN1_TIME_diff(&diff, NULL, NULL, tm) != 1) { | |
| return "0"; | |
| } | |
| #else | |
| apr_time_t then, now = apr_time_now(); | |
| apr_time_exp_t exp = {0}; | |
| long diff; | |
| unsigned char *dp; | |
| #define DIGIT2NUM(x) (((x)[0] - '0') * 10 + (x)[1] - '0') | |
| if (INVALID_ASN1_TIME(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); | |
| 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)); | |
| #endif | |
| 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) { | |
| const 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 | |
| uint64_t rtt; | |
| if (SSL_get_handshake_rtt(ssl, &rtt) > 0) | |
| return apr_psprintf(p, "%" APR_UINT64_T_FMT, (apr_uint64_t)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, | |
| const X509_NAME *xn, apr_pool_t *p) | |
| { | |
| const 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(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, const ASN1_OCTET_STRING *str) | |
| { | |
| const unsigned char *pp = ASN1_STRING_get0_data(str); | |
| 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, ASN1_STRING_length(str))) { | |
| 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++) { | |
| MODSSL_X509_EXT_CONST 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; | |
| } | |