Subject: Patches and Enhancements for a SSL-Proxy Based on Apache 2.0 (mod_ssl, mod_proxy, mod_headers)

From: Maik Mueller <[email-masked]>
Date: Wed, 19 Feb 2003 17:08:56 +0100

Hello All,

I want to provide updated information to my earlier described scenario using
mod_ssl + mod_proxy + mod_headers:
Component:	Web Browser --- Proxy (mod_proxy)       --- Web Server
SSL Role:	SSL Client  --- SSL server | SSL Client --- SSL Server

The following discussion focuses on Apache 2.0.43 and 2.0.44.

I have implemented a solution to transfer the Web browser's client
certificate (and other SSL information) to the backend Web server:
Component:	Web Browser --- Proxy (mod_proxy)       --- Web Server
SSL Role:	SSL Client  --- SSL server | SSL Client --- SSL Server
				Client Cert (and other SSL information) --> Transfer as HTTP Headers

The problem was that mod_headers' RequestHeader directive didn't really
matched the requirements.
RequestHeader set SSL_CLIENT_CERT %{SSL_CLIENT_CERT}e is not a practical
solution to
forward the client's certificate to the backend server for the
following reasons:
1. SSL_CLIENT_CERT produces multi-line output and the RequestHeader
directive isn't able to transfer it into a correct multi-line HTTP header.
2. The "decorations" (-----BEGIN/END CERTIFICATE-----) and the multi-line
format are not very useful in this scenario.

Therefore I have introduced the option "E" in addition to "e" for putting
environment variables in headers. The "E" has the following meaning:

%{FOOBAR}E	The base64 encoded content of the environment variable FOOBAR.
If the environment variable already contains a base64 encoded body (e. g.
SSL_CLIENT_CERT) the body will be set as the value of the header variable.

The result is in any case a single line of base64 characters only.

This behavior serves two requirements:
1. There is no problem escaping special characters when putting other SSL
information in HTTP headers. In many cases, SSL_CLIENT_S_DN will probably
contain characters that have to be escaped.
2. Reduces the overhead produced by "decorations" and multi-line format.

Here is an example for forwarding the SSL Client Certificate and other SSL
information:
    RequestHeader set SSL_CLIENT_CERT         %{SSL_CLIENT_CERT}E
env=SSL_CLIENT_S_DN
    RequestHeader set SSL_CLIENT_CERT_CHAIN_0 %{SSL_CLIENT_CERT_CHAIN_0}E
env=SSL_CLIENT_CERT_CHAIN_0
    RequestHeader set SSL_CLIENT_CERT_CHAIN_1 %{SSL_CLIENT_CERT_CHAIN_1}E
env=SSL_CLIENT_CERT_CHAIN_1
    RequestHeader set SSL_CIPHER_USEKEYSIZE   %{SSL_CIPHER_USEKEYSIZE}e
env=SSL_CIPHER_USEKEYSIZE
    RequestHeader set SSL_CIPHER_SUITE        %{SSL_CIPHER}e
env=SSL_CIPHER

To make this work I also patched two other things:
1. mod_headers' RequestHeader directive wasn't able to take an env clause as
a forth argument in contrast to the Header directive. I don't know the
reason
for that behavior, but env clause seams to work fine with the SSL
environment
variables for RequestHeaders. This was necessary to avoid an empty header if
the environment variable isn't present.
If there are objections, let me know.
2. SSL_CLIENT_CERT_CHAIN_n is broken. To me it seems that somebody has tried
to change SSL_CLIENT_CERT_CHAINn to SSL_CLIENT_CERT_CHAIN_n. However, the
introduction of the "_" wasn't quite consistent. I patched that and now I
can see the intermediate CAs as SSL_CLIENT_CERT_CHAIN_0 to
SSL_CLIENT_CERT_CHAIN_n in the environment.

Last but not least I have updated the mod_headers documentation with the new
option "E" and an example for forwarding the Web browser's client
certificate and some other SSL information.

I think the described patches and enhancements are quite reasonable and I
would like to make them part of the standard Apache distribution. I have
already produced a patch file that works for Apache 2.0.43 and 2.0.44. I
would appreciate guidance on how to proceed.

Comments welcome!

Regards,
	Maik

Maik Mueller
Development Architect
SAP

______________________________________________________________________
Apache Interface to OpenSSL (mod_ssl)                   www.modssl.org
User Support Mailing List                      [email-masked]
Automated List Manager                            [email-masked]

---

From: Maik Mueller <[email-masked]>
Date: Wed, 19 Feb 2003 17:10:12 +0100

> Cool..

> Can you please post the patch to the list, so that ppl can review the
> code,
> and give their comments.

> -Madhu
No problem!
Here is my short README describing the patch and its history form Apache
version 2.0.43 to 2.0.44:

Hello!
This is the distribution point for the Apache 2.0 as SSL Intermediary Patch.
Currently you need this patch to use Apache 2.0 as a trusted intermediary
in configuration with the SAP J2EE Engine.
The patch is subject to become part of the standard Apache 2.0 distribution.

Feedback welcome!
	Maik ([email-masked])

INSTRUCTIONS:
- extract the Apache 2.0.43 distribution (httpd-2.0.43.tar.gz)
- change directory to httpd-2.0.43
- apply the patch with -p1 (patch -p1 < Apache-2.0.43-SSLintermediary.patch)
- follow the Apache INSTALL instructions

HISTORY:
02-12-30 initial release (available SAP internal)
03-01-07 httpd-2.0.43-patched-as-SSLintermediary.zip added
         In this ZIP archive the Apache-2.0.43-SSLintermediary.patch is
         already applied. More convenient for users not so familiar with the
         usage of diff & patch.
03-01-08 httpd-2.0.43-win32-src-patched-as-SSLintermediary.zip added
         You cannot use the UNIX source to build the WIN32 binaries.
         This ZIP archive contains the already patched version of
         httpd-2.0.43-win32-src. Use it to build the WIN32 binaries.
         If you want to apply Apache-2.0.43-SSLintermediary.patch to the
         original httpd-2.0.43-win32-src be aware that you have to convert
         CR-LFs in CR before applying the patch. In the successfully patched
         files you can again expand CR to CR-LF.
03-01-20 Bug in base 64 padding found. The calculation of the number of
padding
         characters ('=') needed computes wrong results in some cases.
03-02-07 Apache 2.0.44 Released
         Apache-2.0.44-SSLintermediary.patch corresponds to
httpd-2.0.44.tar.gz
         The documentation changes are NO longer part of the patch.
         Download mod_headers_mai.html.en for proposed documentation
changes.
         SSLproxy.conf is a good example for a proxy's mod_ssl
configuration.
         The SAP proposed header names are use in the example added to the
         mod_headers documentation (see mod_headers_mai.html.en).

And here follows the patch (My proposed changes to the HTML docu are now not
included in the patch. Please advice me if and how to post this changes to
mod_headers.html.en):
--- httpd-2.0.44.ori/modules/metadata/mod_headers.c	Mon Nov  4 19:31:57 2002
+++ httpd-2.0.44/modules/metadata/mod_headers.c	Fri Feb  7 18:00:18 2003
@@ -109,6 +109,7 @@
 #include "apr_lib.h"
 #include "apr_strings.h"
 #include "apr_buckets.h"
+#include "apr_base64.h"

 #include "apr_hash.h"
 #define APR_WANT_STRFUNC
@@ -198,6 +199,62 @@
     else
         return "(null)";
 }
+
+/* Base 64 encoded ASN.1 data is usually tagged with decorations of
+ * the following style:
+ *   -----BEGIN <description>-----
+ *   <base64 encoded body>
+ *   -----END <description>-----
+ * The defines are used to search for such decorations.
+ */
+#define DECORATION_MARKER_BEGIN "-----BEGIN"
+#define DECORATION_MARKER_END   "-----END"
+#define DECORATION_EOF_MARKER   "-----"
+
+static const char *header_request_env_varB64(request_rec *r, char *a)
+{
+  const char *s = apr_table_get(r->subprocess_env,a);
+  char *pStartBody = NULL;
+  char *pBehindBody = NULL;
+  char *ptr;
+
+  if (s) {
+    /* search for decorations marking encapsulated base64 encoded data */
+    ptr = strstr((char *)s, DECORATION_MARKER_BEGIN);
+    if (ptr) {
+      ptr = strstr(ptr + strlen(DECORATION_MARKER_BEGIN),
DECORATION_EOF_MARKER);
+      if (ptr && (ptr + strlen(DECORATION_EOF_MARKER) + 1) != '\0') {
+	/* explicit check that there are sitll chars in the string */
+	pStartBody = ptr + strlen(DECORATION_EOF_MARKER) + 1;
+
+	ptr = strstr(pStartBody, DECORATION_MARKER_END);
+	if (ptr && strstr(ptr, DECORATION_EOF_MARKER))
+	  pBehindBody = ptr;
+      }
+    }
+
+    if (pStartBody && pBehindBody) {
+      /* encapsulated base64 encoded data found */
+      /* all except the body will be skipped */
+      *pBehindBody = '\0';
+      apr_base64_cleanB64(pStartBody);
+      return pStartBody;
+    } else {
+      /* call apr_base64_encode() to encode the data */
+      int inlen = strlen(s);
+      int outsize = apr_base64_encode_len(inlen);
+      char *encoded = apr_palloc(r->pool, outsize);
+      int rc = apr_base64_encode(encoded, s, inlen);
+      if (rc > outsize)
+	return "(null)";
+      else
+	return encoded;
+    }
+  }
+  else
+    return "(null)";
+}
+
 /*
  * Config routines
  */
@@ -407,7 +464,7 @@

     /* Handle the envclause on Header */
     if (envclause != NULL) {
-        if (inout != hdr_out) {
+        if (inout != hdr_out && inout != hdr_in) {
             return "error: envclause (env=...) only valid on Header
directive";
         }
         if (strncasecmp(envclause, "env=", 4) != 0) {
@@ -448,12 +505,23 @@
     return header_inout_cmd(hdr_out, cmd, indirconf, action, hdr, val,
envclause);
 }

-/* handle RequestHeader directive */
+/* handle RequestHeader directive (enable env clause) */
 static const char *request_header_cmd(cmd_parms *cmd, void *indirconf,
-                              const char *action, const char *inhdr,
-                              const char *value)
+                                      const char *args)
 {
-    return header_inout_cmd(hdr_in, cmd, indirconf, action, inhdr, value,
NULL);
+    const char *s;
+    const char *action;
+    const char *hdr;
+    const char *val;
+    const char *envclause;
+
+    s = apr_pstrdup(cmd->pool, args);
+    action = ap_getword_conf(cmd->pool, &s);
+    hdr = ap_getword_conf(cmd->pool, &s);
+    val = *s ? ap_getword_conf(cmd->pool, &s) : NULL;
+    envclause = *s ? ap_getword_conf(cmd->pool, &s) : NULL;
+
+    return header_inout_cmd(hdr_in, cmd, indirconf, action, hdr, val,
envclause);
 }

 /*
@@ -595,8 +663,8 @@
 {
     AP_INIT_RAW_ARGS("Header", header_cmd, NULL, OR_FILEINFO,
                    "an action, header and value followed by optional env
clause"),
-    AP_INIT_TAKE23("RequestHeader", request_header_cmd, NULL, OR_FILEINFO,
-                   "an action, header and value"),
+    AP_INIT_RAW_ARGS("RequestHeader", request_header_cmd, NULL,
OR_FILEINFO,
+                   "an action, header and value followed by optional env
clause"),
     {NULL}
 };

@@ -612,6 +680,7 @@
     register_format_tag_handler(p, "D", (void*) header_request_duration,
0);
     register_format_tag_handler(p, "t", (void*) header_request_time, 0);
     register_format_tag_handler(p, "e", (void*) header_request_env_var, 0);
+    register_format_tag_handler(p, "E", (void*) header_request_env_varB64,
0);

     return OK;
 }
--- httpd-2.0.43.ori/srclib/apr-util/encoding/apr_base64.c	Wed Mar 13
21:40:47 2002
+++ httpd-2.0.43/srclib/apr-util/encoding/apr_base64.c	Sat Dec 28 18:18:21
2002
@@ -229,6 +229,31 @@
     return nbytesdecoded;
 }

+/* This function removes all non-base64 characters.
+ * Useful to get rid of spaces, CR, LF and other formatting characters.
+ */
+APU_DECLARE(int) apr_base64_cleanB64(char *bufcoded)
+{
+  char *ptr = bufcoded;
+  int len = strlen(bufcoded);
+  int i;
+
+  for (i = 0; i < len; i++)
+    if (pr2six[bufcoded[i]] <= 63)
+      *ptr++ = bufcoded[i];
+
+  /* reapply correct base64 padding */
+  switch ((ptr - bufcoded) % 4) {
+  case 2: /* TWO padding characters needed */
+    *ptr++ = '=';
+  case 3: /* ONE padding character needed */
+    *ptr++ = '=';
+  }
+  *ptr++ = '\0';
+
+  return (ptr - bufcoded);
+}
+
 static const char basis_64[] =
     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

--- httpd-2.0.43.ori/srclib/apr-util/include/apr_base64.h	Wed Mar 13
21:40:48 2002
+++ httpd-2.0.43/srclib/apr-util/include/apr_base64.h	Sun Dec 29 09:31:57
2002
@@ -142,6 +142,14 @@
 APU_DECLARE(int) apr_base64_decode_binary(unsigned char * plain_dst,
                                         const char *coded_src);

+/**
+ * This function removes all non-base64 characters.
+ * Useful to get rid of spaces, CR, LF and other formatting characters.
+ * @param bufcoded The encoded string (input/output)
+ * @return The new length of the encoded string
+ */
+APU_DECLARE(int) apr_base64_cleanB64(char *bufcoded);
+
 /** @} */
 #ifdef __cplusplus
 }
--- httpd-2.0.43.ori/modules/ssl/ssl_engine_vars.c	Tue May 28 23:47:31 2002
+++ httpd-2.0.43/modules/ssl/ssl_engine_vars.c	Sun Dec 29 15:42:59 2002
@@ -479,6 +479,9 @@

     result = NULL;

+    /* Fix inconsistency with SSL_CLIENT_CERT_CHAIN
+       Ignore '_' between SSL_CLIENT_CERT_CHAIN and the subsequent number.
*/
+    if (*var == '_') var++;
     if (strspn(var, "0123456789") == strlen(var)) {
         n = atoi(var);
         if (n < sk_X509_num(sk)) {

Regards,
	Maik