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 },