From 18d7158809c9722f4c6d2a8af7513577274f9b56 Mon Sep 17 00:00:00 2001 From: "Dr. Stephen Henson" Date: Fri, 29 Jun 2012 14:24:42 +0000 Subject: [PATCH] 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. --- CHANGES | 9 ++ apps/s_apps.h | 8 ++ apps/s_cb.c | 243 +++++++++++++++++++++++++++++++++++ apps/s_server.c | 13 ++ demos/certs/apps/mkxcerts.sh | 29 +++++ ssl/s3_clnt.c | 7 + ssl/s3_srvr.c | 8 ++ ssl/ssl.h | 3 + ssl/ssl_cert.c | 9 ++ ssl/ssl_err.c | 1 + ssl/ssl_lib.c | 10 ++ ssl/ssl_locl.h | 11 ++ ssl/t1_lib.c | 5 + ssl/tls1.h | 2 + 14 files changed, 358 insertions(+) create mode 100644 demos/certs/apps/mkxcerts.sh diff --git a/CHANGES b/CHANGES index c690bb3800..2fe4847248 100644 --- a/CHANGES +++ b/CHANGES @@ -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 diff --git a/apps/s_apps.h b/apps/s_apps.h index 8c644ec7db..3491b1ab69 100644 --- a/apps/s_apps.h +++ b/apps/s_apps.h @@ -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); diff --git a/apps/s_cb.c b/apps/s_cb.c index 79e8ae55a7..34c0185559 100644 --- a/apps/s_cb.c +++ b/apps/s_cb.c @@ -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; + } + diff --git a/apps/s_server.c b/apps/s_server.c index f190d8e0d9..649a8a9c9e 100644 --- a/apps/s_server.c +++ b/apps/s_server.c @@ -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); diff --git a/demos/certs/apps/mkxcerts.sh b/demos/certs/apps/mkxcerts.sh new file mode 100644 index 0000000000..88fb1c57c7 --- /dev/null +++ b/demos/certs/apps/mkxcerts.sh @@ -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 diff --git a/ssl/s3_clnt.c b/ssl/s3_clnt.c index c51f3d0b0f..8d7bcfef39 100644 --- a/ssl/s3_clnt.c +++ b/ssl/s3_clnt.c @@ -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 diff --git a/ssl/s3_srvr.c b/ssl/s3_srvr.c index 6ae2c4d0f0..879d074e63 100644 --- a/ssl/s3_srvr.c +++ b/ssl/s3_srvr.c @@ -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)); diff --git a/ssl/ssl.h b/ssl/ssl.h index 3675d6e453..afeb60de7b 100644 --- a/ssl/ssl.h +++ b/ssl/ssl.h @@ -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 diff --git a/ssl/ssl_cert.c b/ssl/ssl_cert.c index 89a5131119..9aa7b04966 100644 --- a/ssl/ssl_cert.c +++ b/ssl/ssl_cert.c @@ -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; diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c index 3b32f55e44..34695e8ebf 100644 --- a/ssl/ssl_err.c +++ b/ssl/ssl_err.c @@ -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"}, diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index b3836b7e68..18e80d4ddc 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -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; diff --git a/ssl/ssl_locl.h b/ssl/ssl_locl.h index c2547ad47f..17bbbf5af4 100644 --- a/ssl/ssl_locl.h +++ b/ssl/ssl_locl.h @@ -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); diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c index 46b3a4c9f9..6b0ddf2631 100644 --- a/ssl/t1_lib.c +++ b/ssl/t1_lib.c @@ -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 diff --git a/ssl/tls1.h b/ssl/tls1.h index e2acad4d70..4d087e0a2b 100644 --- a/ssl/tls1.h +++ b/ssl/tls1.h @@ -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) -- GitLab