Add certificate callback. If set this is called whenever a certificate
authorDr. Stephen Henson <steve@openssl.org>
Wed, 26 Dec 2012 14:43:51 +0000 (14:43 +0000)
committerDr. Stephen Henson <steve@openssl.org>
Wed, 26 Dec 2012 14:43:51 +0000 (14:43 +0000)
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.
(backport from HEAD)

13 files changed:
CHANGES
apps/s_apps.h
apps/s_cb.c
apps/s_server.c
ssl/s3_clnt.c
ssl/s3_srvr.c
ssl/ssl.h
ssl/ssl_cert.c
ssl/ssl_err.c
ssl/ssl_lib.c
ssl/ssl_locl.h
ssl/t1_lib.c
ssl/tls1.h

diff --git a/CHANGES b/CHANGES
index 9ebc5a3f7c66575ea602f06b4f89be33616b3409..b47ccac9b5b58fad87bcc7ae34aab7ac82b11c13 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -8,6 +8,15 @@
      OID NID.
      [Steve Henson]
 
+  *) 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
index 8c644ec7dbb20c24ef534b8afc750ee266150eff..3491b1ab6904dfb3ed6817a35b9bed23ae4bc108 100644 (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);
index bb9064b67a1714090828dc86027a73832697a395..9a73197286993685f0dc9cb9cc98b7988fc6aa4a 100644 (file)
@@ -1039,3 +1039,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;
+       }
+
index b9751bc10e34a2b3aa8d606e7cc511c6ec6cfc83..7016757cf552d371eff584ebee90dad9c8fc7a1f 100644 (file)
@@ -981,6 +981,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;
@@ -1131,6 +1132,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)
@@ -1433,6 +1440,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,
@@ -1594,6 +1604,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.
         */
@@ -1666,6 +1677,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.
                 */
@@ -2009,6 +2021,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);
index 0acaca5a123953d2ef64c64f3997e26ff8235adb..8984183c1e6d91aa89773e35686e442fcb598b2e 100644 (file)
@@ -3181,6 +3181,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
index cd623a81b0a48b233440bb176713db36825a22ae..f6ab29cd79ba63e60cabd2af0e8d2d7fa7ff39fa 100644 (file)
@@ -1358,6 +1358,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));
index 9ca50b74e0df2d071731abc4dd1c7d9459b87827..b3f2108f07f01c77ed52b26f83f4ab4bac174d74 100644 (file)
--- a/ssl/ssl.h
+++ b/ssl/ssl.h
@@ -1791,6 +1791,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
@@ -1886,6 +1887,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
@@ -2443,6 +2445,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
index 89a51311196a9fe5b68a7668c7844b986cf49460..9aa7b04966e026a7f3cb0feaf7dd3fe92cb43ea9 100644 (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;
index 046b9f9dcd6c74fb2fbc0c19104762421a42770c..e78a7ac2934fadb1fb77537a3f708788f17785d4 100644 (file)
@@ -357,6 +357,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"},
index 585411afc37126e8490b1daad723e0465801dd23..ea5dd0bd694cb2aaf68f2f3bc301afd5ea7ae928 100644 (file)
@@ -2047,6 +2047,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;
index 49ad5fdb89749c6c908087fd03f41286ed9e7ef1..a81c4e2ab74dc39cfbf6cbecef2c16db3583b2f6 100644 (file)
@@ -552,6 +552,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;
 
@@ -890,6 +900,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);
index 6e7abc7e97d0109e760504781741b18d45618c90..846dbf7519d43393687070ebe50464cbac5c8946 100644 (file)
@@ -3551,5 +3551,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
index e2acad4d70505481d48242eb9140bee271b8df93..4d087e0a2bd14b00dc87c25e29e63825374d9829 100644 (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)