summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjames <james@e7ae566f-a301-0410-adde-c780ea21d3b5>2009-10-25 15:51:04 +0000
committerjames <james@e7ae566f-a301-0410-adde-c780ea21d3b5>2009-10-25 15:51:04 +0000
commitec4a500bb4f0c642fb4e013387d97aab3c516372 (patch)
treec56bc3a8991d0afe025e946f8a6b61296487dde7
parent0c7168e259036d9c9e7571d5a3e2bfecde67c9bd (diff)
downloadopenvpn-ec4a500bb4f0c642fb4e013387d97aab3c516372.tar.gz
openvpn-ec4a500bb4f0c642fb4e013387d97aab3c516372.tar.xz
openvpn-ec4a500bb4f0c642fb4e013387d97aab3c516372.zip
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
-rw-r--r--multi.c3
-rw-r--r--ssl.c137
-rw-r--r--ssl.h21
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
@@ -340,6 +340,104 @@ tmp_rsa_cb (SSL * s, int is_export, int keylength)
}
/*
+ * 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.
*
* Example:
@@ -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
@@ -303,6 +303,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