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