I've noticed that sending CA chains (of potentially untrusted certificates), with a client certificate on a per-connection basis is currently impossible, as per the BUGS section of the SSL_CTX_set_client_cert_cb(3) page and bug #270 in the request tracker. This doesn't seem to be a difficult change (but it does change the size of the SSL structure), and I was wondering if something like the attached patch could be included in future releases.
Note that this isn't currently tested, but if it looks OK I can check it actually works properly (and write my code using it :) ). Thanks -- Andrew Oakley
diff -ur openssl-1.0.0-beta3/include/openssl/ssl.h openssl-1.0.0-beta3-extra-certs/include/openssl/ssl.h --- openssl-1.0.0-beta3/include/openssl/ssl.h 2009-07-15 12:32:57.000000000 +0100 +++ openssl-1.0.0-beta3-extra-certs/include/openssl/ssl.h 2009-09-03 16:21:37.000000000 +0100 @@ -1106,6 +1106,8 @@ /* for server side, keep the list of CA_dn we can use */ STACK_OF(X509_NAME) *client_CA; + STACK_OF(X509) *extra_certs; + int references; unsigned long options; /* protocol behaviour */ unsigned long mode; /* API behaviour */ @@ -1426,6 +1428,9 @@ #define SSL_CTX_add_extra_chain_cert(ctx,x509) \ SSL_CTX_ctrl(ctx,SSL_CTRL_EXTRA_CHAIN_CERT,0,(char *)x509) +#define SSL_add_extra_chain_cert(ctx,x509) \ + SSL_ctrl(ctx,SSL_CTRL_EXTRA_CHAIN_CERT,0,(char *)x509) + #ifndef OPENSSL_NO_BIO BIO_METHOD *BIO_f_ssl(void); BIO *BIO_new_ssl(SSL_CTX *ctx,int client); diff -ur openssl-1.0.0-beta3/ssl/d1_both.c openssl-1.0.0-beta3-extra-certs/ssl/d1_both.c --- openssl-1.0.0-beta3/ssl/d1_both.c 2009-07-15 12:32:57.000000000 +0100 +++ openssl-1.0.0-beta3-extra-certs/ssl/d1_both.c 2009-09-03 16:25:41.000000000 +0100 @@ -837,6 +837,7 @@ int i; unsigned long l= 3 + DTLS1_HM_HEADER_LENGTH; BUF_MEM *buf; + STACK_OF(X509) *extras; /* TLSv1 sends a chain with nothing in it, instead of an alert */ buf=s->init_buf; @@ -868,10 +869,14 @@ } X509_STORE_CTX_cleanup(&xs_ctx); } - /* Thawte special :-) */ - for (i=0; i<sk_X509_num(s->ctx->extra_certs); i++) + + if (s->extra_certs) + extras = s->extra_certs; + else + extras = s->ctx->extra_certs; + for (i=0; i<sk_X509_num(extras); i++) { - x=sk_X509_value(s->ctx->extra_certs,i); + x=sk_X509_value(extras,i); if (!dtls1_add_cert_to_buf(buf, &l, x)) return 0; } diff -ur openssl-1.0.0-beta3/ssl/s3_both.c openssl-1.0.0-beta3-extra-certs/ssl/s3_both.c --- openssl-1.0.0-beta3/ssl/s3_both.c 2009-07-15 12:32:57.000000000 +0100 +++ openssl-1.0.0-beta3-extra-certs/ssl/s3_both.c 2009-09-03 16:00:50.000000000 +0100 @@ -288,8 +288,14 @@ unsigned long l=7; BUF_MEM *buf; int no_chain; + STACK_OF(X509) *extras; - if ((s->mode & SSL_MODE_NO_AUTO_CHAIN) || s->ctx->extra_certs) + if (s->extra_certs) + extras = s->extra_certs; + else + extras = s->ctx->extra_certs; + + if ((s->mode & SSL_MODE_NO_AUTO_CHAIN) || extras) no_chain = 1; else no_chain = 0; @@ -332,9 +338,9 @@ } } /* Thawte special :-) */ - for (i=0; i<sk_X509_num(s->ctx->extra_certs); i++) + for (i=0; i<sk_X509_num(extras); i++) { - x=sk_X509_value(s->ctx->extra_certs,i); + x=sk_X509_value(extras,i); if (ssl3_add_cert_to_buf(buf, &l, x)) return(0); } diff -ur openssl-1.0.0-beta3/ssl/s3_lib.c openssl-1.0.0-beta3-extra-certs/ssl/s3_lib.c --- openssl-1.0.0-beta3/ssl/s3_lib.c 2009-05-28 19:10:47.000000000 +0100 +++ openssl-1.0.0-beta3-extra-certs/ssl/s3_lib.c 2009-09-03 16:16:29.000000000 +0100 @@ -2477,6 +2477,16 @@ break; #endif /* !OPENSSL_NO_TLSEXT */ + + case SSL_CTRL_EXTRA_CHAIN_CERT: + if (s->extra_certs == NULL) + { + if ((s->extra_certs=sk_X509_new_null()) == NULL) + return(0); + } + sk_X509_push(s->extra_certs,(X509 *)parg); + break; + default: break; } diff -ur openssl-1.0.0-beta3/ssl/ssl.h openssl-1.0.0-beta3-extra-certs/ssl/ssl.h --- openssl-1.0.0-beta3/ssl/ssl.h 2009-07-15 12:32:57.000000000 +0100 +++ openssl-1.0.0-beta3-extra-certs/ssl/ssl.h 2009-09-03 16:21:37.000000000 +0100 @@ -1106,6 +1106,8 @@ /* for server side, keep the list of CA_dn we can use */ STACK_OF(X509_NAME) *client_CA; + STACK_OF(X509) *extra_certs; + int references; unsigned long options; /* protocol behaviour */ unsigned long mode; /* API behaviour */ @@ -1426,6 +1428,9 @@ #define SSL_CTX_add_extra_chain_cert(ctx,x509) \ SSL_CTX_ctrl(ctx,SSL_CTRL_EXTRA_CHAIN_CERT,0,(char *)x509) +#define SSL_add_extra_chain_cert(ctx,x509) \ + SSL_ctrl(ctx,SSL_CTRL_EXTRA_CHAIN_CERT,0,(char *)x509) + #ifndef OPENSSL_NO_BIO BIO_METHOD *BIO_f_ssl(void); BIO *BIO_new_ssl(SSL_CTX *ctx,int client); diff -ur openssl-1.0.0-beta3/ssl/ssl_lib.c openssl-1.0.0-beta3-extra-certs/ssl/ssl_lib.c --- openssl-1.0.0-beta3/ssl/ssl_lib.c 2009-06-30 12:57:24.000000000 +0100 +++ openssl-1.0.0-beta3-extra-certs/ssl/ssl_lib.c 2009-09-03 16:28:23.000000000 +0100 @@ -357,6 +357,9 @@ s->verify_result=X509_V_OK; + s->client_CA = NULL; + s->extra_certs = NULL; + s->method=ctx->method; if (!s->method->ssl_new(s)) @@ -577,6 +580,8 @@ if (s->client_CA != NULL) sk_X509_NAME_pop_free(s->client_CA,X509_NAME_free); + if (s->extra_certs != NULL) + sk_X509_pop_free(s->extra_certs,X509_free); if (s->method != NULL) s->method->ssl_free(s); @@ -2393,6 +2398,7 @@ { STACK_OF(X509_NAME) *sk; X509_NAME *xn; + X509 *x; SSL *ret; int i; @@ -2510,6 +2516,20 @@ } } + if (s->extra_certs != NULL) + { + if ((ret->extra_certs=sk_X509_dup(s->extra_certs)) == NULL) goto err; + for (i=0; i<sk_X509_num(s->extra_certs); i++) + { + x=sk_X509_value(s->extra_certs,i); + if (sk_X509_set(s->extra_certs,i,X509_dup(x)) == NULL) + { + X509_free(x); + goto err; + } + } + } + if (0) { err: