On Monday 29 November 2010, Dr Stephen Henson wrote:
> On 24/11/2010 07:07, Kaspar Brand wrote:
> > On 20.11.2010 20:24, Stefan Fritsch wrote:
> >> On Fri, 19 Nov 2010, Joe Orton wrote:
> >>> We could support this better by having a new set of exports:
> >>>   SSL_{CLIENT,SERVER}_{I,S}_UTF8DN_*(_n)?
> >>> 
> >>> (or something similarly named)
> >>> 
> >>> which works the same as _DN_ but exports the attributes as a
> >>> UTF-8 byte seequence regardless of the underlying ASN.1 type;
> >>> this would be a relatively simple hack.
> >> 
> >> Or have a (per vhost) directive that enables conversion for all
> >> SSL_*_S_DN_* and SSL_*_S_DN to UTF8. IMHO, this could even be
> >> enabled by default in 2.4.
> > 
> > I prefer the latter approach, yes (there's already an awful lot
> > of SSL_* something variables).
> > 
> > Given the fact that mod_ssl's current behavior with non-ASCII
> > characters (i.e., outside the 0-127 range) is mostly undefined
> > and/or sometimes even erroneous (a BMPString in the subject or
> > issuer DN will end up as an empty SSL_*_*_DN_* variable, due to
> > the initial null byte), I would suggest the following solution:
> > 
> > - for all SSL_{CLIENT,SERVER}_{I,S}_DN_* variables, use UTF-8 by
> > default
> > 
> >   (i.e., adapt ssl_engine_vars.c:ssl_var_lookup_ssl_cert_dn() to
> >   convert TeletexString, UniversalString and BMPString types to
> >   UTF8String)
> > 
> > - for SSL_{CLIENT,SERVER}_{I,S}_DN, don't use X509_NAME_oneline()
> > 
> >   any more and switch to X509_NAME_print_ex() instead. What flags
> >   should be used is probably debatable - I would recommend to go
> >   with XN_FLAG_RFC2253 (note that using XN_FLAG_ONELINE with
> >   X509_NAME_print_ex doesn't produce the same string as
> >   X509_NAME_oneline, so this will break backward compatibility
> >   in any case)
> > 
> > - add another parameter to the SSLOptions directive which allows
> > 
> >   re-enabling the "old" string rendering for
> >   SSL_{CLIENT,SERVER}_{I,S}_DN (so the new default would be to
> >   rely on X509_NAME_print_ex - even for 2.2 -, but people could
> >   restore the current behavior through this option)
> 
> It's a very good idea to avoid X509_NAME_oneline() wherever
> possible as it is highly broken, can produce ambiguous output (of
> the Bobby Tables variety) and at other times be just plain wrong
> (BMPStrings is one of many examples).
> 
> We have to retain it in OpenSSL for backwards compatibility though.
> I'd throw it out tomorrow if I could get away with it.
> 
> You can get a UTF8String from most string types using
> ASN1_STRING_to_UTF8(). This should be adequate for most purposes:
> it doesn't handle the more bizarre TeletexString shift conversions
> but those are rarely encountered in practice.

I have come up with the attached patch which more or less implements 
Kaspar's suggestions. I am using
"XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB" as flags for 
X509_NAME_print_ex() for SSL_{CLIENT,SERVER}_{I,S}_DN. This changes

/C=US/ST=California/L=San Francisco/O=ASF/OU=httpd-
test/CN=ca/emailaddress=test-...@httpd.apache.org

into

emailaddress=test-...@httpd.apache.org,CN=ca,OU=httpd-test,O=ASF,L=San 
Francisco,ST=California,C=US

Is this what we want? We could use XN_FLAG_DN_REV to keep the old 
order. I haven't tried UTF8 characters, yet.

Instead of an SSLOption (which would require a request_rec to lookup), 
I have implemented a per-vhost option for restoring compatibility.
diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c
index 3d090cb..fc36f6c 100644
--- a/modules/ssl/mod_ssl.c
+++ b/modules/ssl/mod_ssl.c
@@ -128,6 +128,8 @@ static const command_rec ssl_config_cmds[] = {
                 "Use the server's cipher ordering preference")
     SSL_CMD_SRV(InsecureRenegotiation, FLAG,
                 "Enable support for insecure renegotiation")
+    SSL_CMD_SRV(LegacyDNVarFormat, FLAG,
+                "Use legacy format for SSL_{CLIENT,SERVER}_{I,S}_DN variables")
     SSL_CMD_ALL(UserName, TAKE1,
                 "Set user name to SSL variable value")
     SSL_CMD_SRV(StrictSNIVHostCheck, FLAG,
diff --git a/modules/ssl/ssl_engine_config.c b/modules/ssl/ssl_engine_config.c
index 4bccfe7..de4b621 100644
--- a/modules/ssl/ssl_engine_config.c
+++ b/modules/ssl/ssl_engine_config.c
@@ -186,6 +186,7 @@ static SSLSrvConfigRec *ssl_config_server_new(apr_pool_t *p)
     sc->session_cache_timeout  = UNSET;
     sc->cipher_server_pref     = UNSET;
     sc->insecure_reneg         = UNSET;
+    sc->legacy_dn_format       = UNSET;
     sc->proxy_ssl_check_peer_expire = SSL_ENABLED_UNSET;
     sc->proxy_ssl_check_peer_cn     = SSL_ENABLED_UNSET;
 #ifndef OPENSSL_NO_TLSEXT
@@ -298,6 +299,7 @@ void *ssl_config_server_merge(apr_pool_t *p, void *basev, void *addv)
     cfgMergeInt(session_cache_timeout);
     cfgMergeBool(cipher_server_pref);
     cfgMergeBool(insecure_reneg);
+    cfgMergeBool(legacy_dn_format);
     cfgMerge(proxy_ssl_check_peer_expire, SSL_ENABLED_UNSET);
     cfgMerge(proxy_ssl_check_peer_cn, SSL_ENABLED_UNSET);
 #ifndef OPENSSL_NO_TLSEXT
@@ -669,6 +671,13 @@ const char *ssl_cmd_SSLInsecureRenegotiation(cmd_parms *cmd, void *dcfg, int fla
 #endif
 }
 
+const char *ssl_cmd_SSLLegacyDNVarFormat(cmd_parms *cmd, void *dcfg, int flag)
+{
+    SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+    sc->legacy_dn_format = flag?TRUE:FALSE;
+    return NULL;
+}
+
 
 static const char *ssl_cmd_check_dir(cmd_parms *parms,
                                      const char **dir)
diff --git a/modules/ssl/ssl_engine_vars.c b/modules/ssl/ssl_engine_vars.c
index d5cf63b..30bdd81 100644
--- a/modules/ssl/ssl_engine_vars.c
+++ b/modules/ssl/ssl_engine_vars.c
@@ -40,7 +40,7 @@
 */
 
 static char *ssl_var_lookup_ssl(apr_pool_t *p, conn_rec *c, char *var);
-static char *ssl_var_lookup_ssl_cert(apr_pool_t *p, X509 *xs, char *var);
+static char *ssl_var_lookup_ssl_cert(apr_pool_t *p, X509 *xs, char *var, int legacy_format);
 static char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, X509_NAME *xsname, char *var);
 static char *ssl_var_lookup_ssl_cert_valid(apr_pool_t *p, ASN1_UTCTIME *tm);
 static char *ssl_var_lookup_ssl_cert_remain(apr_pool_t *p, ASN1_UTCTIME *tm);
@@ -358,13 +358,19 @@ static char *ssl_var_lookup_ssl(apr_pool_t *p, conn_rec *c, char *var)
     }
     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, xs, var+7);
+            SSLSrvConfigRec *sc = mySrvConfigFromConn(c);
+            result = ssl_var_lookup_ssl_cert(p, xs, var+7, sc->legacy_dn_format);
             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, xs, var+7);
+        if ((xs = SSL_get_certificate(ssl)) != NULL) {
+            SSLSrvConfigRec *sc = mySrvConfigFromConn(c);
+            result = ssl_var_lookup_ssl_cert(p, xs, var+7, sc->legacy_dn_format);
+	    /* 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);
@@ -386,13 +392,39 @@ static char *ssl_var_lookup_ssl(apr_pool_t *p, conn_rec *c, char *var)
     return result;
 }
 
-static char *ssl_var_lookup_ssl_cert(apr_pool_t *p, X509 *xs, char *var)
+static char *ssl_var_lookup_ssl_cert_dn_oneline(apr_pool_t *p,
+                                                X509_NAME *xsname,
+                                                int legacy_dn_format)
+{
+    char *result;
+    if (legacy_dn_format == TRUE) {
+        char *cp = X509_NAME_oneline(xsname, NULL, 0);
+        result = apr_pstrdup(p, cp);
+        modssl_free(cp);
+    }
+    else {
+        BIO* bio;
+        int n;
+        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);
+        n = BIO_pending(bio);
+        result = apr_pcalloc(p, n+1);
+        n = BIO_read(bio, result, n);
+        result[n] = NUL;
+        BIO_free(bio);
+    }
+    return result;
+}
+
+static char *ssl_var_lookup_ssl_cert(apr_pool_t *p, X509 *xs, char *var,
+                                     int legacy_format)
 {
     char *result;
     BOOL resdup;
     X509_NAME *xsname;
     int nid;
-    char *cp;
 
     result = NULL;
     resdup = TRUE;
@@ -416,9 +448,7 @@ static char *ssl_var_lookup_ssl_cert(apr_pool_t *p, X509 *xs, char *var)
     }
     else if (strcEQ(var, "S_DN")) {
         xsname = X509_get_subject_name(xs);
-        cp = X509_NAME_oneline(xsname, NULL, 0);
-        result = apr_pstrdup(p, cp);
-        modssl_free(cp);
+        result = ssl_var_lookup_ssl_cert_dn_oneline(p, xsname, legacy_format);
         resdup = FALSE;
     }
     else if (strlen(var) > 5 && strcEQn(var, "S_DN_", 5)) {
@@ -428,9 +458,7 @@ static char *ssl_var_lookup_ssl_cert(apr_pool_t *p, X509 *xs, char *var)
     }
     else if (strcEQ(var, "I_DN")) {
         xsname = X509_get_issuer_name(xs);
-        cp = X509_NAME_oneline(xsname, NULL, 0);
-        result = apr_pstrdup(p, cp);
-        modssl_free(cp);
+        result = ssl_var_lookup_ssl_cert_dn_oneline(p, xsname, legacy_format);
         resdup = FALSE;
     }
     else if (strlen(var) > 5 && strcEQn(var, "I_DN_", 5)) {
@@ -516,13 +544,16 @@ static char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, X509_NAME *xsname, char *
                 n =OBJ_obj2nid((ASN1_OBJECT *)X509_NAME_ENTRY_get_object(xsne));
 
                 if (n == ssl_var_lookup_ssl_cert_dn_rec[i].nid && idx-- == 0) {
-                    unsigned char *data = X509_NAME_ENTRY_get_data_ptr(xsne);
-                    /* cast needed from unsigned char to char */
-                    result = apr_pstrmemdup(p, (char *)data,
-                                            X509_NAME_ENTRY_get_data_len(xsne));
+                    unsigned char *string = NULL;
+                    int ret = ASN1_STRING_to_UTF8(&string, X509_NAME_ENTRY_get_data(xsne));
+                    if (ret >= 0) {
+                        /* cast needed from unsigned char to char */
+                        result = apr_pstrmemdup(p, (char *)string, ret);
+                        modssl_free(string);
 #if APR_CHARSET_EBCDIC
-                    ap_xlate_proto_from_ascii(result, X509_NAME_ENTRY_get_data_len(xsne));
+                        ap_xlate_proto_from_ascii(result, ret);
 #endif /* APR_CHARSET_EBCDIC */
+                    }
                     break;
                 }
             }
diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h
index ae344a1..f390808 100644
--- a/modules/ssl/ssl_private.h
+++ b/modules/ssl/ssl_private.h
@@ -528,6 +528,7 @@ struct SSLSrvConfigRec {
     int              session_cache_timeout;
     BOOL             cipher_server_pref;
     BOOL             insecure_reneg;
+    BOOL             legacy_dn_format;
     modssl_ctx_t    *server;
     modssl_ctx_t    *proxy;
     ssl_enabled_t    proxy_ssl_check_peer_expire;
@@ -602,7 +603,8 @@ const char  *ssl_cmd_SSLRequire(cmd_parms *, void *, const char *);
 const char  *ssl_cmd_SSLUserName(cmd_parms *, void *, const char *);
 const char  *ssl_cmd_SSLRenegBufferSize(cmd_parms *cmd, void *dcfg, const char *arg);
 const char  *ssl_cmd_SSLStrictSNIVHostCheck(cmd_parms *cmd, void *dcfg, int flag);
-const char *ssl_cmd_SSLInsecureRenegotiation(cmd_parms *cmd, void *dcfg, int flag);
+const char  *ssl_cmd_SSLInsecureRenegotiation(cmd_parms *cmd, void *dcfg, int flag);
+const char  *ssl_cmd_SSLLegacyDNVarFormat(cmd_parms *cmd, void *dcfg, int flag);
 
 const char  *ssl_cmd_SSLProxyEngine(cmd_parms *cmd, void *dcfg, int flag);
 const char  *ssl_cmd_SSLProxyProtocol(cmd_parms *, void *, const char *);

Reply via email to