From ec4a500bb4f0c642fb4e013387d97aab3c516372 Mon Sep 17 00:00:00 2001 From: james Date: Sun, 25 Oct 2009 15:51:04 +0000 Subject: On server, lock client-provided certs against mid-session TLS renegotiations -- this is similer to how the common name is also locked. git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@5105 e7ae566f-a301-0410-adde-c780ea21d3b5 --- multi.c | 3 +- ssl.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- ssl.h | 21 +++++++++- 3 files changed, 156 insertions(+), 5 deletions(-) diff --git a/multi.c b/multi.c index 7f77cb8..7ea64b4 100644 --- a/multi.c +++ b/multi.c @@ -1458,8 +1458,9 @@ multi_connection_established (struct multi_context *m, struct multi_instance *mi ASSERT (mi->context.c1.tuntap); - /* lock down the common name so it can't change during future TLS renegotiations */ + /* lock down the common name and cert hashes so they can't change during future TLS renegotiations */ tls_lock_common_name (mi->context.c2.tls_multi); + tls_lock_cert_hash_set (mi->context.c2.tls_multi); /* generate a msg() prefix for this client instance */ generate_prefix (mi); diff --git a/ssl.c b/ssl.c index 847c5ec..d882c94 100644 --- a/ssl.c +++ b/ssl.c @@ -339,6 +339,104 @@ tmp_rsa_cb (SSL * s, int is_export, int keylength) return (rsa_tmp); } +/* + * Cert hash functions + */ +static void +cert_hash_remember (struct tls_session *session, const int error_depth, const unsigned char *sha1_hash) +{ + if (error_depth >= 0 && error_depth < MAX_CERT_DEPTH) + { + if (!session->cert_hash_set) + ALLOC_OBJ_CLEAR (session->cert_hash_set, struct cert_hash_set); + if (!session->cert_hash_set->ch[error_depth]) + ALLOC_OBJ (session->cert_hash_set->ch[error_depth], struct cert_hash); + { + struct cert_hash *ch = session->cert_hash_set->ch[error_depth]; + memcpy (ch->sha1_hash, sha1_hash, SHA_DIGEST_LENGTH); + } + } +} + +#if 0 +static void +cert_hash_print (const struct cert_hash_set *chs, int msglevel) +{ + struct gc_arena gc = gc_new (); + msg (msglevel, "CERT_HASH"); + if (chs) + { + int i; + for (i = 0; i < MAX_CERT_DEPTH; ++i) + { + const struct cert_hash *ch = chs->ch[i]; + if (ch) + msg (msglevel, "%d:%s", i, format_hex(ch->sha1_hash, SHA_DIGEST_LENGTH, 0, &gc)); + } + } + gc_free (&gc); +} +#endif + +static void +cert_hash_free (struct cert_hash_set *chs) +{ + if (chs) + { + int i; + for (i = 0; i < MAX_CERT_DEPTH; ++i) + free (chs->ch[i]); + free (chs); + } +} + +static bool +cert_hash_compare (const struct cert_hash_set *chs1, const struct cert_hash_set *chs2) +{ + if (chs1 && chs2) + { + int i; + for (i = 0; i < MAX_CERT_DEPTH; ++i) + { + const struct cert_hash *ch1 = chs1->ch[i]; + const struct cert_hash *ch2 = chs2->ch[i]; + + if (!ch1 && !ch2) + continue; + else if (ch1 && ch2 && !memcmp (ch1->sha1_hash, ch2->sha1_hash, SHA_DIGEST_LENGTH)) + continue; + else + return false; + } + return true; + } + else if (!chs1 && !chs2) + return true; + else + return false; +} + +static struct cert_hash_set * +cert_hash_copy (const struct cert_hash_set *chs) +{ + struct cert_hash_set *dest = NULL; + if (chs) + { + int i; + ALLOC_OBJ_CLEAR (dest, struct cert_hash_set); + for (i = 0; i < MAX_CERT_DEPTH; ++i) + { + const struct cert_hash *ch = chs->ch[i]; + if (ch) + { + ALLOC_OBJ (dest->ch[i], struct cert_hash); + memcpy (dest->ch[i]->sha1_hash, ch->sha1_hash, SHA_DIGEST_LENGTH); + } + } + } + return dest; +} + /* * Extract a field from an X509 subject name. * @@ -603,7 +701,7 @@ verify_callback (int preverify_ok, X509_STORE_CTX * ctx) SSL *ssl; struct tls_session *session; const struct tls_options *opt; - const int max_depth = 8; + const int max_depth = MAX_CERT_DEPTH; struct argv argv = argv_new (); /* get the tls_session pointer */ @@ -645,9 +743,16 @@ verify_callback (int preverify_ok, X509_STORE_CTX * ctx) string_mod_sslname (common_name, COMMON_NAME_CHAR_CLASS, opt->ssl_flags); + cert_hash_remember (session, ctx->error_depth, ctx->current_cert->sha1_hash); + #if 0 /* print some debugging info */ - msg (D_LOW, "LOCAL OPT: %s", opt->local_options); - msg (D_LOW, "X509: %s", subject); + { + struct gc_arena gc = gc_new (); + msg (M_INFO, "LOCAL OPT[%d]: %s", ctx->error_depth, opt->local_options); + msg (M_INFO, "X509[%d]: %s", ctx->error_depth, subject); + msg (M_INFO, "SHA1[%d]: %s", ctx->error_depth, format_hex(ctx->current_cert->sha1_hash, SHA_DIGEST_LENGTH, 0, &gc)); + gc_free (&gc); + } #endif /* did peer present cert which was signed our root cert? */ @@ -898,6 +1003,14 @@ tls_lock_common_name (struct tls_multi *multi) multi->locked_cn = string_alloc (cn, NULL); } +void +tls_lock_cert_hash_set (struct tls_multi *multi) +{ + const struct cert_hash_set *chs = multi->session[TM_ACTIVE].cert_hash_set; + if (chs && !multi->locked_cert_hash_set) + multi->locked_cert_hash_set = cert_hash_copy (chs); +} + static bool tls_lock_username (struct tls_multi *multi, const char *username) { @@ -2251,6 +2364,8 @@ tls_session_free (struct tls_session *session, bool clear) if (session->common_name) free (session->common_name); + cert_hash_free (session->cert_hash_set); + if (clear) CLEAR (*session); } @@ -2444,6 +2559,8 @@ tls_multi_free (struct tls_multi *multi, bool clear) if (multi->locked_username) free (multi->locked_username); + cert_hash_free (multi->locked_cert_hash_set); + for (i = 0; i < TM_SIZE; ++i) tls_session_free (&multi->session[i], false); @@ -3492,6 +3609,20 @@ key_method_2_read (struct buffer *buf, struct tls_multi *multi, struct tls_sessi } } + /* Don't allow the cert hashes to change once they have been locked */ + if (ks->authenticated && multi->locked_cert_hash_set) + { + const struct cert_hash_set *chs = session->cert_hash_set; + if (chs && !cert_hash_compare (chs, multi->locked_cert_hash_set)) + { + msg (D_TLS_ERRORS, "TLS Auth Error: TLS object CN=%s client-provided SSL certs unexpectedly changed during mid-session reauth", + session->common_name); + + /* disable the tunnel */ + tls_deauthenticate (multi); + } + } + /* verify --client-config-dir based authentication */ if (ks->authenticated && session->opt->client_config_dir_exclusive) { diff --git a/ssl.h b/ssl.h index 7e0bfb5..3bb5fbe 100644 --- a/ssl.h +++ b/ssl.h @@ -302,6 +302,21 @@ */ /* #define MEASURE_TLS_HANDSHAKE_STATS */ +/* + * Keep track of certificate hashes at various depths + */ + +/* Maximum certificate depth we will allow */ +#define MAX_CERT_DEPTH 8 + +struct cert_hash { + unsigned char sha1_hash[SHA_DIGEST_LENGTH]; +}; + +struct cert_hash_set { + struct cert_hash *ch[MAX_CERT_DEPTH]; +}; + /* * Key material, used as source for PRF-based * key expansion. @@ -518,6 +533,8 @@ struct tls_session char *common_name; + struct cert_hash_set *cert_hash_set; + #ifdef ENABLE_PF uint32_t common_name_hashval; #endif @@ -589,10 +606,11 @@ struct tls_multi int n_soft_errors; /* errors due to unrecognized or failed-to-authenticate incoming packets */ /* - * Our locked common name and username (cannot change during the life of this tls_multi object) + * Our locked common name, username, and cert hashes (cannot change during the life of this tls_multi object) */ char *locked_cn; char *locked_username; + struct cert_hash_set *locked_cert_hash_set; #ifdef ENABLE_DEF_AUTH /* @@ -692,6 +710,7 @@ bool tls_rec_payload (struct tls_multi *multi, const char *tls_common_name (const struct tls_multi* multi, const bool null); void tls_set_common_name (struct tls_multi *multi, const char *common_name); void tls_lock_common_name (struct tls_multi *multi); +void tls_lock_cert_hash_set (struct tls_multi *multi); #define TLS_AUTHENTICATION_SUCCEEDED 0 #define TLS_AUTHENTICATION_FAILED 1 -- cgit v1.2.3