Add certificate callback. If set this is called whenever a certificate

is required by client or server. An application can decide which
certificate chain to present based on arbitrary criteria: for example
supported signature algorithms. Add very simple example to s_server.
This fixes many of the problems and restrictions of the existing client
certificate callback: for example you can now clear existing certificates
and specify the whole chain.
This commit is contained in:
Dr. Stephen Henson 2012-06-29 14:24:42 +00:00
parent 0f39bab0df
commit 18d7158809
14 changed files with 358 additions and 0 deletions

View File

@ -4,6 +4,15 @@
Changes between 1.0.1 and 1.1.0 [xx XXX xxxx]
*) Add certificate callback. If set this is called whenever a certificate
is required by client or server. An application can decide which
certificate chain to present based on arbitrary criteria: for example
supported signature algorithms. Add very simple example to s_server.
This fixes many of the problems and restrictions of the existing client
certificate callback: for example you can now clear an existing
certificate and specify the whole chain.
[Steve Henson]
*) Add new "valid_flags" field to CERT_PKEY structure which determines what
the certificate can be used for (if anything). Set valid_flags field
in new tls1_check_chain function. Simplify ssl_set_cert_masks which used

View File

@ -181,3 +181,11 @@ void MS_CALLBACK tlsext_cb(SSL *s, int client_server, int type,
int MS_CALLBACK generate_cookie_callback(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len);
int MS_CALLBACK verify_cookie_callback(SSL *ssl, unsigned char *cookie, unsigned int cookie_len);
typedef struct ssl_excert_st SSL_EXCERT;
void ssl_ctx_set_excert(SSL_CTX *ctx, SSL_EXCERT *exc);
void ssl_excert_free(SSL_EXCERT *exc);
int args_excert(char ***pargs, int *pargc,
int *badarg, BIO *err, SSL_EXCERT **pexc);
int load_excert(SSL_EXCERT **pexc, BIO *err);

View File

@ -1037,3 +1037,246 @@ int MS_CALLBACK verify_cookie_callback(SSL *ssl, unsigned char *cookie, unsigned
return 0;
}
/* Example of extended certificate handling. Where the standard support
* of one certificate per algorithm is not sufficient an application
* can decide which certificate(s) to use at runtime based on whatever
* criteria it deems appropriate.
*/
/* Linked list of certificates, keys and chains */
struct ssl_excert_st
{
int certform;
const char *certfile;
int keyform;
const char *keyfile;
const char *chainfile;
X509 *cert;
EVP_PKEY *key;
STACK_OF(X509) *chain;
struct ssl_excert_st *next, *prev;
};
/* Very basic selection callback: just use any certificate chain
* reported as valid. More sophisticated could prioritise according
* to local policy.
*/
static int set_cert_cb(SSL *ssl, void *arg)
{
SSL_EXCERT *exc = arg;
SSL_certs_clear(ssl);
if (!exc)
return 1;
/* Go to end of list and traverse backwards since we prepend
* newer entries this retains the original order.
*/
while (exc->next)
exc = exc->next;
while(exc)
{
if (SSL_check_chain(ssl, exc->cert, exc->key, exc->chain))
{
SSL_use_certificate(ssl, exc->cert);
SSL_use_PrivateKey(ssl, exc->key);
if (exc->chain)
SSL_set1_chain(ssl, exc->chain);
}
exc = exc->prev;
}
return 1;
}
void ssl_ctx_set_excert(SSL_CTX *ctx, SSL_EXCERT *exc)
{
SSL_CTX_set_cert_cb(ctx, set_cert_cb, exc);
}
static int ssl_excert_prepend(SSL_EXCERT **pexc)
{
SSL_EXCERT *exc;
exc = OPENSSL_malloc(sizeof(SSL_EXCERT));
if (!exc)
return 0;
exc->certfile = NULL;
exc->keyfile = NULL;
exc->chainfile = NULL;
exc->cert = NULL;
exc->key = NULL;
exc->chain = NULL;
exc->prev = NULL;
exc->next = *pexc;
*pexc = exc;
if (exc->next)
{
exc->certform = exc->next->certform;
exc->keyform = exc->next->keyform;
exc->next->prev = exc;
}
else
{
exc->certform = FORMAT_PEM;
exc->keyform = FORMAT_PEM;
}
return 1;
}
void ssl_excert_free(SSL_EXCERT *exc)
{
SSL_EXCERT *curr;
while (exc)
{
if (exc->cert)
X509_free(exc->cert);
if (exc->key)
EVP_PKEY_free(exc->key);
if (exc->chain)
sk_X509_pop_free(exc->chain, X509_free);
curr = exc;
exc = exc->next;
OPENSSL_free(curr);
}
}
int load_excert(SSL_EXCERT **pexc, BIO *err)
{
SSL_EXCERT *exc = *pexc;
if (!exc)
return 1;
/* If nothing in list, free and set to NULL */
if (!exc->certfile && !exc->next)
{
ssl_excert_free(exc);
*pexc = NULL;
return 1;
}
for(; exc; exc=exc->next)
{
if (!exc->certfile)
{
BIO_printf(err, "Missing filename\n");
return 0;
}
exc->cert = load_cert(err, exc->certfile, exc->certform,
NULL, NULL, "Server Certificate");
if (!exc->cert)
return 0;
if (exc->keyfile)
exc->keyfile = exc->certfile;
exc->key = load_key(err, exc->certfile, exc->certform, 0,
NULL, NULL, "Server Certificate");
if (!exc->key)
return 0;
if (exc->chainfile)
{
exc->chain = load_certs(err,
exc->chainfile, FORMAT_PEM,
NULL, NULL,
"Server Chain");
if (!exc->chainfile)
return 0;
}
}
return 1;
}
int args_excert(char ***pargs, int *pargc,
int *badarg, BIO *err, SSL_EXCERT **pexc)
{
char *arg = **pargs, *argn = (*pargs)[1];
SSL_EXCERT *exc = *pexc;
if (!exc && !ssl_excert_prepend(&exc))
{
BIO_printf(err, "Error initialising xcert\n");
*badarg = 1;
goto err;
}
if (strcmp(arg, "-xcert") == 0)
{
if (!argn)
{
*badarg = 1;
return 1;
}
if (exc->certfile && !ssl_excert_prepend(&exc))
{
BIO_printf(err, "Error adding xcert\n");
*badarg = 1;
goto err;
}
exc->certfile = argn;
}
else if (strcmp(arg,"-xkey") == 0)
{
if (!argn)
{
*badarg = 1;
return 1;
}
if (exc->keyfile)
{
BIO_printf(err, "Key already specified\n");
*badarg = 1;
return 1;
}
exc->keyfile = argn;
}
else if (strcmp(arg,"-xchain") == 0)
{
if (!argn)
{
*badarg = 1;
return 1;
}
if (exc->chainfile)
{
BIO_printf(err, "Chain already specified\n");
*badarg = 1;
return 1;
}
exc->chainfile = argn;
}
else if (strcmp(arg,"-xcertform") == 0)
{
if (!argn)
{
*badarg = 1;
goto err;
}
exc->certform = str2fmt(argn);
}
else if (strcmp(arg,"-xkeyform") == 0)
{
if (!argn)
{
*badarg = 1;
goto err;
}
exc->keyform = str2fmt(argn);
}
else
return 0;
(*pargs) += 2;
if (pargc)
*pargc -= 2;
*pexc = exc;
return 1;
err:
ERR_print_errors(err);
ssl_excert_free(exc);
*pexc = NULL;
return 1;
}

View File

@ -991,6 +991,7 @@ int MAIN(int argc, char *argv[])
char *srpuserseed = NULL;
char *srp_verifier_file = NULL;
#endif
SSL_EXCERT *exc = NULL;
meth=SSLv23_server_method();
local_argc=argc;
@ -1143,6 +1144,12 @@ int MAIN(int argc, char *argv[])
goto bad;
continue;
}
else if (args_excert(&argv, &argc, &badarg, bio_err, &exc))
{
if (badarg)
goto bad;
continue;
}
else if (strcmp(*argv,"-verify_return_error") == 0)
verify_return_error = 1;
else if (strcmp(*argv,"-serverpref") == 0)
@ -1456,6 +1463,9 @@ bad:
s_key_file2 = s_cert_file2;
#endif
if (!load_excert(&exc, bio_err))
goto end;
if (nocert == 0)
{
s_key = load_key(bio_err, s_key_file, s_key_format, 0, pass, e,
@ -1618,6 +1628,7 @@ bad:
if (hack) SSL_CTX_set_options(ctx,SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG);
SSL_CTX_set_options(ctx,off);
if (cert_flags) SSL_CTX_set_cert_flags(ctx, cert_flags);
if (exc) ssl_ctx_set_excert(ctx, exc);
/* DTLS: partial reads end up discarding unread UDP bytes :-(
* Setting read ahead solves this problem.
*/
@ -1692,6 +1703,7 @@ bad:
if (hack) SSL_CTX_set_options(ctx2,SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG);
SSL_CTX_set_options(ctx2,off);
if (cert_flags) SSL_CTX_set_cert_flags(ctx2, cert_flags);
if (exc) ssl_ctx_set_excert(ctx2, exc);
/* DTLS: partial reads end up discarding unread UDP bytes :-(
* Setting read ahead solves this problem.
*/
@ -2035,6 +2047,7 @@ end:
if (authz_in != NULL)
BIO_free(authz_in);
#endif
ssl_excert_free(exc);
if (bio_s_out != NULL)
{
BIO_free(bio_s_out);

View File

@ -0,0 +1,29 @@
# Create certificates using various algorithms to test multi-certificate
# functionality.
OPENSSL=../../../apps/openssl
CN="OpenSSL Test RSA SHA-1 cert" $OPENSSL req \
-config apps.cnf -extensions usr_cert -x509 -nodes \
-keyout tsha1.pem -out tsha1.pem -new -days 3650 -sha1
CN="OpenSSL Test RSA SHA-256 cert" $OPENSSL req \
-config apps.cnf -extensions usr_cert -x509 -nodes \
-keyout tsha256.pem -out tsha256.pem -new -days 3650 -sha256
CN="OpenSSL Test RSA SHA-512 cert" $OPENSSL req \
-config apps.cnf -extensions usr_cert -x509 -nodes \
-keyout tsha512.pem -out tsha512.pem -new -days 3650 -sha512
# Create EC parameters
$OPENSSL ecparam -name P-256 -out ecp256.pem
$OPENSSL ecparam -name P-384 -out ecp384.pem
CN="OpenSSL Test P-256 SHA-256 cert" $OPENSSL req \
-config apps.cnf -extensions usr_cert -x509 -nodes \
-nodes -keyout tecp256.pem -out tecp256.pem -newkey ec:ecp256.pem \
-days 3650 -sha256
CN="OpenSSL Test P-384 SHA-384 cert" $OPENSSL req \
-config apps.cnf -extensions usr_cert -x509 -nodes \
-nodes -keyout tecp384.pem -out tecp384.pem -newkey ec:ecp384.pem \
-days 3650 -sha384

View File

@ -3180,6 +3180,13 @@ int ssl3_send_client_certificate(SSL *s)
if (s->state == SSL3_ST_CW_CERT_A)
{
/* Let cert callback update client certificates if required */
if (s->cert->cert_cb
&& s->cert->cert_cb(s, s->cert->cert_cb_arg) <= 0)
{
ssl3_send_alert(s,SSL3_AL_FATAL,SSL_AD_INTERNAL_ERROR);
return 0;
}
if (ssl3_check_client_certificate(s))
s->state=SSL3_ST_CW_CERT_C;
else

View File

@ -1357,6 +1357,14 @@ int ssl3_get_client_hello(SSL *s)
SSLerr(SSL_F_SSL3_GET_CLIENT_HELLO,SSL_R_NO_CIPHERS_PASSED);
goto f_err;
}
/* Let cert callback update server certificates if required */
if (s->cert->cert_cb
&& s->cert->cert_cb(s, s->cert->cert_cb_arg) <= 0)
{
al=SSL_AD_INTERNAL_ERROR;
SSLerr(SSL_F_SSL3_GET_CLIENT_HELLO,SSL_R_CERT_CB_ERROR);
goto f_err;
}
ciphers=NULL;
c=ssl3_choose_cipher(s,s->session->ciphers,
SSL_get_ciphers(s));

View File

@ -1800,6 +1800,7 @@ int (*SSL_get_verify_callback(const SSL *s))(int,X509_STORE_CTX *);
void SSL_set_verify(SSL *s, int mode,
int (*callback)(int ok,X509_STORE_CTX *ctx));
void SSL_set_verify_depth(SSL *s, int depth);
void SSL_set_cert_cb(SSL *s, int (*cb)(SSL *ssl, void *arg), void *arg);
#ifndef OPENSSL_NO_RSA
int SSL_use_RSAPrivateKey(SSL *ssl, RSA *rsa);
#endif
@ -1895,6 +1896,7 @@ void SSL_CTX_set_verify(SSL_CTX *ctx,int mode,
int (*callback)(int, X509_STORE_CTX *));
void SSL_CTX_set_verify_depth(SSL_CTX *ctx,int depth);
void SSL_CTX_set_cert_verify_callback(SSL_CTX *ctx, int (*cb)(X509_STORE_CTX *,void *), void *arg);
void SSL_CTX_set_cert_cb(SSL_CTX *c, int (*cb)(SSL *ssl, void *arg), void *arg);
#ifndef OPENSSL_NO_RSA
int SSL_CTX_use_RSAPrivateKey(SSL_CTX *ctx, RSA *rsa);
#endif
@ -2462,6 +2464,7 @@ void ERR_load_SSL_strings(void);
#define SSL_R_CA_DN_TOO_LONG 132
#define SSL_R_CCS_RECEIVED_EARLY 133
#define SSL_R_CERTIFICATE_VERIFY_FAILED 134
#define SSL_R_CERT_CB_ERROR 377
#define SSL_R_CERT_LENGTH_MISMATCH 135
#define SSL_R_CHALLENGE_IS_DIFFERENT 136
#define SSL_R_CIPHER_CODE_WRONG_LENGTH 137

View File

@ -379,6 +379,9 @@ CERT *ssl_cert_dup(CERT *cert)
ret->cert_flags = cert->cert_flags;
ret->cert_cb = cert->cert_cb;
ret->cert_cb_arg = cert->cert_cb_arg;
return(ret);
#if !defined(OPENSSL_NO_DH) || !defined(OPENSSL_NO_ECDH)
@ -557,6 +560,12 @@ int ssl_cert_add1_chain_cert(CERT *c, X509 *x)
return 1;
}
void ssl_cert_set_cert_cb(CERT *c, int (*cb)(SSL *ssl, void *arg), void *arg)
{
c->cert_cb = cb;
c->cert_cb_arg = arg;
}
SESS_CERT *ssl_sess_cert_new(void)
{
SESS_CERT *ret;

View File

@ -356,6 +356,7 @@ static ERR_STRING_DATA SSL_str_reasons[]=
{ERR_REASON(SSL_R_CA_DN_TOO_LONG) ,"ca dn too long"},
{ERR_REASON(SSL_R_CCS_RECEIVED_EARLY) ,"ccs received early"},
{ERR_REASON(SSL_R_CERTIFICATE_VERIFY_FAILED),"certificate verify failed"},
{ERR_REASON(SSL_R_CERT_CB_ERROR) ,"cert cb error"},
{ERR_REASON(SSL_R_CERT_LENGTH_MISMATCH) ,"cert length mismatch"},
{ERR_REASON(SSL_R_CHALLENGE_IS_DIFFERENT),"challenge is different"},
{ERR_REASON(SSL_R_CIPHER_CODE_WRONG_LENGTH),"cipher code wrong length"},

View File

@ -2048,6 +2048,16 @@ void SSL_CTX_set_verify_depth(SSL_CTX *ctx,int depth)
X509_VERIFY_PARAM_set_depth(ctx->param, depth);
}
void SSL_CTX_set_cert_cb(SSL_CTX *c, int (*cb)(SSL *ssl, void *arg), void *arg)
{
ssl_cert_set_cert_cb(c->cert, cb, arg);
}
void SSL_set_cert_cb(SSL *s, int (*cb)(SSL *ssl, void *arg), void *arg)
{
ssl_cert_set_cert_cb(s->cert, cb, arg);
}
void ssl_set_cert_masks(CERT *c, const SSL_CIPHER *cipher)
{
CERT_PKEY *cpk;

View File

@ -550,6 +550,16 @@ typedef struct cert_st
TLS_SIGALGS *shared_sigalgs;
size_t shared_sigalgslen;
/* Certificate setup callback: if set is called whenever a
* certificate may be required (client or server). the callback
* can then examine any appropriate parameters and setup any
* certificates required. This allows advanced applications
* to select certificates on the fly: for example based on
* supported signature algorithms or curves.
*/
int (*cert_cb)(SSL *ssl, void *arg);
void *cert_cb_arg;
int references; /* >1 only if SSL_copy_session_id is used */
} CERT;
@ -888,6 +898,7 @@ int ssl_cert_set0_chain(CERT *c, STACK_OF(X509) *chain);
int ssl_cert_set1_chain(CERT *c, STACK_OF(X509) *chain);
int ssl_cert_add0_chain_cert(CERT *c, X509 *x);
int ssl_cert_add1_chain_cert(CERT *c, X509 *x);
void ssl_cert_set_cert_cb(CERT *c, int (*cb)(SSL *ssl, void *arg), void *arg);
int ssl_verify_cert_chain(SSL *s,STACK_OF(X509) *sk);
int ssl_add_cert_chain(SSL *s, CERT_PKEY *cpk, unsigned long *l);

View File

@ -3514,5 +3514,10 @@ void tls1_set_cert_validity(SSL *s)
tls1_check_chain(s, NULL, NULL, NULL, SSL_PKEY_DH_DSA);
tls1_check_chain(s, NULL, NULL, NULL, SSL_PKEY_ECC);
}
/* User level utiity function to check a chain is suitable */
int SSL_check_chain(SSL *s, X509 *x, EVP_PKEY *pk, STACK_OF(X509) *chain)
{
return tls1_check_chain(s, x, pk, chain, -1);
}
#endif

View File

@ -318,6 +318,8 @@ int SSL_get_shared_sigalgs(SSL *s, int idx,
int *psign, int *phash, int *psignandhash,
unsigned char *rsig, unsigned char *rhash);
int SSL_check_chain(SSL *s, X509 *x, EVP_PKEY *pk, STACK_OF(X509) *chain);
#define SSL_set_tlsext_host_name(s,name) \
SSL_ctrl(s,SSL_CTRL_SET_TLSEXT_HOSTNAME,TLSEXT_NAMETYPE_host_name,(char *)name)