Hello,

On Fri, May 09, Willy Tarreau wrote:
> So basically the output format could be built using this string :
> 
>     ssl_c_cert,pem(CERTIFICATE)

What kind of argument the CERTIFICATE should be ? ARGT_STR / ARGT_UINT ?
 
> With :
>    - ssl_c_cert outputting raw binary
>    - pem(type) being the PEM encoder (prepend the BEGIN and append
>      the END line, encode in base64 each block of 48 bytes, and emit
>      a spacexs)

Here's a quick "proof of concept" patch (should apply to
haproxy-ss-20140516 and latest git):
- ssl_c_cert (smp_fetch_ssl_c_cert) function that gets client cert
  in der("raw") format (with openssl i2d_X509).
  (Maybe ssl_c_cert could take optional parameter to retrieve any
  certificate from client certificate chain (with SSL_get_peer_cert_chain)
  Something like: ssl_c_cert(0) or ssl_c_cert -> get client cert
  and ssl_c_cert(1) -> get first intermediate cert in chain.
  (like apache/mod_ssl SSL_CLIENT_CERT_CHAINn)

- sample_conv_bin2pem function that base64 encodes the der/raw cert to
  pem. (This is still ugly... the base64 encoding loop can
  certainly use some cleanup).

Could somebody take a look if the patch makes any sense ? Especially
if I'm using the arguments to sample_conv_bin2pem correctly and that
I'm calculating buffer sizes correctly -> no over/underflows.

With the patch it's possible to add for example SSL_CLIENT_CERT
header:
# Apache/mod_ssl style where pem cert \n is replaced with space
http-request set-header SSL_CLIENT_CERT %[ssl_c_cert,pem(\ ,\ )]
# Pound --enable-cert1l: pem cert with whitespace removed
http-request set-header SSL_CLIENT_CERT %[ssl_c_cert,pem]
# Pound / nginx:
http-request set-header SSL_CLIENT_CERT %[ssl_c_cert,pem(\r\n\t)]

--> pem(\r\n\t) doesn't quite work, because haproxy urlencodes \r\n
to %0D%0A. Is it possible to pass the output w/out urlencoding ?

-Jarno

PS.  Minimal config for testing:
frontend https_8443
        bind *:8443 name test ssl verify optional crt /pathto/server.pem 
ca-file /pathto/ca.pem crt-ignore-err 18,19,20,27,21

        mode http
        http-request set-header SSL_CIPHER %{+Q}[ssl_fc_cipher]
        http-request set-header SSL_CIPHER_USEKEYSIZE %{+Q}[ssl_fc_alg_keysize]
        http-request set-header SSL_CLIENT_CERT %[ssl_c_cert,pem(\ ,\ )]
        #http-request set-header SSL_CLIENT_CERT %[ssl_c_cert,pem(\r\n\t)]

        default_backend BE_idp_tomcat

backend BE_idp_tomcat
        mode http
        server netcat 127.0.0.1:8080
diff --git a/src/sample.c b/src/sample.c
index a1e8012..ae8837f 100644
--- a/src/sample.c
+++ b/src/sample.c
@@ -1208,6 +1208,104 @@ static int sample_conv_bin2hex(const struct arg *arg_p, 
struct sample *smp)
        return 1;
 }
 
+static int sample_conv_bin2pem(const struct arg *arg_p, struct sample *smp)
+{
+       struct chunk *trash = get_trash_chunk();
+       const char *begin_crt = "-----BEGIN CERTIFICATE-----";
+       const char *end_crt = "-----END CERTIFICATE-----";
+       int b64_len;
+       int pem_len;
+       int src_incr = 0;
+       int dst_incr;
+       int i;
+
+       trash->len = 0;
+
+       /* Calc. output length: (base64 calc. from base64.c):
+        * pem_len = strlen(begin_crt) + arg[0].data.str.len + base64_len
+        *           base64_len/64 * arg[0].data.str.len +
+        *           if (base64_len%64) { arg[0].data.str.len } +
+        *           strlen(end_crt) + arg[1].data.str.len */
+       b64_len = ((smp->data.str.len + 2) / 3) * 4;
+
+       pem_len = b64_len;
+       pem_len += strlen(begin_crt) + strlen(end_crt);
+
+       if (arg_p && arg_p[0].type == ARGT_STR) {
+               if (b64_len % 64) {
+                       pem_len += arg_p[0].data.str.len;
+               }
+               pem_len += (b64_len / 64) * arg_p[0].data.str.len;
+
+               pem_len += arg_p[0].data.str.len;
+
+               /* User wants string after ----END CERT...---- line */
+               if (arg_p[1].type == ARGT_STR)
+                       pem_len += arg_p[1].data.str.len;
+       }
+
+       /* fail if output won't fit in chunk */
+       if (pem_len >= trash->size)
+               return 0;
+
+       memcpy(trash->str, begin_crt, strlen(begin_crt));
+       dst_incr = strlen(begin_crt);
+       if (arg_p && arg_p[0].type == ARGT_STR) {
+               memcpy(trash->str+dst_incr, arg_p[0].data.str.str, 
arg_p[0].data.str.len);
+               dst_incr += arg_p[0].data.str.len;
+       }
+
+       /* base64 encode input in 48bytes chunks => 64bytes base64 encoded line 
*/
+       for (i=0; i < (smp->data.str.len / 48); i++) {
+               int tmp_len;
+               tmp_len = a2base64(smp->data.str.str + src_incr, 48, 
trash->str+dst_incr, trash->size - dst_incr);
+               /* abort if a2base64 returns error */
+               if (tmp_len < 0)
+                       return 0;
+
+               dst_incr += tmp_len;
+               /* Append user supplied string to each line */
+               if (arg_p && arg_p[0].type == ARGT_STR) {
+                       memcpy(trash->str+dst_incr, arg_p[0].data.str.str, 
arg_p[0].data.str.len);
+                       dst_incr += arg_p[0].data.str.len;
+               }
+
+               src_incr += 48;
+       }
+
+       /* base64 encode remaining (< 48) input bytes */
+       if (src_incr < smp->data.str.len) {
+               int tmp_len;
+               tmp_len = a2base64(smp->data.str.str + src_incr, 
smp->data.str.len - src_incr, trash->str+dst_incr, trash->size - dst_incr);
+
+               if (tmp_len < 0)
+                       return 0;
+
+               dst_incr += tmp_len;
+               /* Append user supplied string to last base64 encoded line */
+               if (arg_p && arg_p[0].type == ARGT_STR) {
+                       memcpy(trash->str+dst_incr, arg_p[0].data.str.str, 
arg_p[0].data.str.len);
+                       dst_incr += arg_p[0].data.str.len;
+               }
+       }
+
+       /* append -----END CERTIFICATE----- line */
+       memcpy(trash->str+dst_incr, end_crt, strlen(end_crt));
+       dst_incr += strlen(end_crt);
+
+       /* append optional string after -----END CERTIFICATE----- */
+       if (arg_p && arg_p[1].type == ARGT_STR) {
+               memcpy(trash->str+dst_incr, arg_p[1].data.str.str, 
arg_p[1].data.str.len);
+               dst_incr += arg_p[1].data.str.len;
+       }
+
+       trash->len = dst_incr;
+       smp->data.str = *trash;
+       smp->type = SMP_T_STR;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
 static int sample_conv_str2lower(const struct arg *arg_p, struct sample *smp)
 {
        int i;
@@ -1347,11 +1445,12 @@ static struct sample_fetch_kw_list smp_kws = {ILH, {
 
 /* Note: must not be declared <const> as its list will be overwritten */
 static struct sample_conv_kw_list sample_conv_kws = {ILH, {
-       { "base64", sample_conv_bin2base64,0,            NULL, SMP_T_BIN,  
SMP_T_STR  },
-       { "upper",  sample_conv_str2upper, 0,            NULL, SMP_T_STR,  
SMP_T_STR  },
-       { "lower",  sample_conv_str2lower, 0,            NULL, SMP_T_STR,  
SMP_T_STR  },
-       { "hex",    sample_conv_bin2hex,   0,            NULL, SMP_T_BIN,  
SMP_T_STR  },
-       { "ipmask", sample_conv_ipmask,    ARG1(1,MSK4), NULL, SMP_T_IPV4, 
SMP_T_IPV4 },
+       { "base64", sample_conv_bin2base64,0,               NULL, SMP_T_BIN,  
SMP_T_STR  },
+       { "pem",    sample_conv_bin2pem,   ARG2(0,STR,STR), NULL, SMP_T_BIN,  
SMP_T_STR  },
+       { "upper",  sample_conv_str2upper, 0,               NULL, SMP_T_STR,  
SMP_T_STR  },
+       { "lower",  sample_conv_str2lower, 0,               NULL, SMP_T_STR,  
SMP_T_STR  },
+       { "hex",    sample_conv_bin2hex,   0,               NULL, SMP_T_BIN,  
SMP_T_STR  },
+       { "ipmask", sample_conv_ipmask,    ARG1(1,MSK4),    NULL, SMP_T_IPV4, 
SMP_T_IPV4 },
        { NULL, NULL, 0, 0, 0 },
 }};
 
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index fd0b41f..1f1da11 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -2453,6 +2453,57 @@ smp_fetch_ssl_x_key_alg(struct proxy *px, struct session 
*l4, void *l7, unsigned
        return 1;
 }
 
+/* binary, returns the client certificate in der(raw) format. */
+static int
+smp_fetch_ssl_c_cert(struct proxy *px, struct session *l4, void *l7, unsigned 
int opt,
+                        const struct arg *args, struct sample *smp, const char 
*kw)
+{
+       int ret = 0;
+       X509 *crt;
+       int len;
+       unsigned char *buf;
+       struct chunk *smp_trash;
+       struct connection *conn;
+
+       if (!l4)
+               return 0;
+
+       conn = objt_conn(l4->si[0].end);
+       if (!conn || conn->xprt != &ssl_sock)
+               return 0;
+
+       if (!(conn->flags & CO_FL_CONNECTED)) {
+               smp->flags |= SMP_F_MAY_CHANGE;
+               return 0;
+       }
+
+       /* SSL_get_peer_certificate increase X509 * ref count  */
+       crt = SSL_get_peer_certificate(conn->xprt_ctx);
+       if (!crt)
+               return 0;
+
+       len = i2d_X509(crt, NULL);
+
+       smp_trash = get_trash_chunk();
+       buf = (unsigned char *) smp_trash->str;
+
+       if ((len <= 0) || (len >= smp_trash->size))
+               goto end;
+
+       if ((len = i2d_X509(crt, &buf)) <= 0)
+               goto end;
+
+       smp_trash->len = len;
+
+       smp->data.str = *smp_trash;
+       smp->type = SMP_T_BIN;
+       ret = 1;
+
+end:
+       X509_free(crt);
+       return ret;
+}
+
 /* boolean, returns true if front conn. transport layer is SSL.
  * This function is also usable on backend conn if the fetch keyword 5th
  * char is 'b'.
@@ -3489,6 +3540,7 @@ static struct sample_fetch_kw_list sample_fetch_keywords 
= {ILH, {
        { "ssl_bc_session_id",      smp_fetch_ssl_fc_session_id,  0,            
       NULL,    SMP_T_BIN,  SMP_USE_L5SRV },
        { "ssl_c_ca_err",           smp_fetch_ssl_c_ca_err,       0,            
       NULL,    SMP_T_UINT, SMP_USE_L5CLI },
        { "ssl_c_ca_err_depth",     smp_fetch_ssl_c_ca_err_depth, 0,            
       NULL,    SMP_T_UINT, SMP_USE_L5CLI },
+       { "ssl_c_cert",             smp_fetch_ssl_c_cert,         0,            
       NULL,    SMP_T_BIN,  SMP_USE_L5CLI },
        { "ssl_c_err",              smp_fetch_ssl_c_err,          0,            
       NULL,    SMP_T_UINT, SMP_USE_L5CLI },
        { "ssl_c_i_dn",             smp_fetch_ssl_x_i_dn,         
ARG2(0,STR,SINT),    NULL,    SMP_T_STR,  SMP_USE_L5CLI },
        { "ssl_c_key_alg",          smp_fetch_ssl_x_key_alg,      0,            
       NULL,    SMP_T_STR,  SMP_USE_L5CLI },

Reply via email to