Based on the 'IV_NCP=2' mechanism described in http://permalink.gmane.org/gmane.network.openvpn.devel/9385.
This is the first patch of a set that adds support for cipher negatiation. Follow-up patches will add ways to restrict or disable the mechanism, and add server-side support. Signed-off-by: Steffan Karger <stef...@karger.me> --- src/openvpn/crypto.c | 18 +++++++++--- src/openvpn/crypto.h | 4 +++ src/openvpn/crypto_backend.h | 6 ++++ src/openvpn/init.c | 51 +++++++++++++++++++++++---------- src/openvpn/init.h | 3 +- src/openvpn/misc.h | 7 +++++ src/openvpn/mtu.c | 2 -- src/openvpn/multi.c | 2 +- src/openvpn/route.c | 4 +-- src/openvpn/ssl.c | 67 +++++++++++++++++++++++++++++++++++++++----- src/openvpn/ssl.h | 14 +++++++++ src/openvpn/ssl_common.h | 16 +++++++++-- 12 files changed, 159 insertions(+), 35 deletions(-) diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c index 5c392aa..e43c30b 100644 --- a/src/openvpn/crypto.c +++ b/src/openvpn/crypto.c @@ -709,10 +709,6 @@ openvpn_decrypt (struct buffer *buf, struct buffer work, return ret; } -/* - * How many bytes will we add to frame buffer for a given - * set of crypto options? - */ void crypto_adjust_frame_parameters(struct frame *frame, const struct key_type* kt, @@ -746,6 +742,14 @@ crypto_adjust_frame_parameters(struct frame *frame, __func__, crypto_overhead); } +size_t +crypto_max_overhead(void) +{ + return packet_id_size(true) + OPENVPN_MAX_IV_LENGTH + + OPENVPN_MAX_CIPHER_BLOCK_SIZE + + MAX(OPENVPN_MAX_HMAC_SIZE, OPENVPN_AEAD_TAG_LENGTH); +} + /* * Build a struct key_type. */ @@ -774,6 +778,9 @@ init_key_type (struct key_type *kt, const char *ciphername, #endif )) msg (M_FATAL, "Cipher '%s' mode not supported", ciphername); + + if (OPENVPN_MAX_CIPHER_BLOCK_SIZE < cipher_kt_block_size(kt->cipher)) + msg (M_FATAL, "Cipher '%s' not allowed: block size too big.", ciphername); } else { @@ -785,6 +792,9 @@ init_key_type (struct key_type *kt, const char *ciphername, if (!aead_cipher) { /* Ignore auth for AEAD ciphers */ kt->digest = md_kt_get (authname); kt->hmac_length = md_kt_size (kt->digest); + + if (OPENVPN_MAX_HMAC_SIZE < kt->hmac_length) + msg (M_FATAL, "HMAC '%s' not allowed: digest size too big.", authname); } } else if (!aead_cipher) diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h index 63d7040..de433ae 100644 --- a/src/openvpn/crypto.h +++ b/src/openvpn/crypto.h @@ -226,6 +226,7 @@ struct key_ctx_bi * direction. */ struct key_ctx decrypt; /**< cipher and/or HMAC contexts for * receiving direction. */ + bool initialized; }; /** @@ -385,6 +386,7 @@ bool openvpn_decrypt (struct buffer *buf, struct buffer work, /** @} name Functions for performing security operations on data channel packets */ +/** Calculate crypto overhead and adjust frame to account for that */ void crypto_adjust_frame_parameters(struct frame *frame, const struct key_type* kt, bool cipher_defined, @@ -392,6 +394,8 @@ void crypto_adjust_frame_parameters(struct frame *frame, bool packet_id, bool packet_id_long_form); +/** Return the worst-case OpenVPN crypto overhead (in bytes) */ +size_t crypto_max_overhead(void); /* Minimum length of the nonce used by the PRNG */ #define NONCE_SECRET_LEN_MIN 16 diff --git a/src/openvpn/crypto_backend.h b/src/openvpn/crypto_backend.h index 3893f14..a699673 100644 --- a/src/openvpn/crypto_backend.h +++ b/src/openvpn/crypto_backend.h @@ -41,6 +41,12 @@ /* TLS uses a tag of 128 bytes, let's do the same for OpenVPN */ #define OPENVPN_AEAD_TAG_LENGTH 16 +/* Maximum cipher block size (bytes) */ +#define OPENVPN_MAX_CIPHER_BLOCK_SIZE 32 + +/* Maximum HMAC digest size (bytes) */ +#define OPENVPN_MAX_HMAC_SIZE 64 + /** Struct used in cipher name translation table */ typedef struct { const char *openvpn_name; /**< Cipher name used by OpenVPN */ diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 50cbf90..5eded09 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -1819,6 +1819,7 @@ pull_permission_mask (const struct context *c) | OPT_P_SHAPER | OPT_P_TIMER | OPT_P_COMP + | OPT_P_CRYPTO | OPT_P_PERSIST | OPT_P_MESSAGES | OPT_P_EXPLICIT_NOTIFY @@ -1922,6 +1923,13 @@ do_deferred_options (struct context *c, const unsigned int found) " MTU problems", TUN_MTU_SIZE(&c->c2.frame) ); } } + + /* process (potenitally pushed) crypto options */ + if (c->options.pull) + { + tls_session_update_crypto_params(&c->c2.tls_multi->session[TM_ACTIVE], + &c->options, &c->c2.frame); + } #endif } @@ -2027,6 +2035,7 @@ frame_finalize_options (struct context *c, const struct options *o) |FRAME_HEADROOM_MARKER_READ_STREAM); } + frame_add_to_extra_buffer (&c->c2.frame, PAYLOAD_ALIGN); frame_finalize (&c->c2.frame, o->ce.link_mtu_defined, o->ce.link_mtu, @@ -2282,12 +2291,13 @@ do_init_crypto_tls (struct context *c, const unsigned int flags) /* In short form, unique datagram identifier is 32 bits, in long form 64 bits */ packet_id_long_form = cipher_kt_mode_ofb_cfb (c->c1.ks.key_type.cipher); - /* Compute MTU parameters */ - crypto_adjust_frame_parameters (&c->c2.frame, - &c->c1.ks.key_type, - options->ciphername_defined, - options->use_iv, - options->replay, packet_id_long_form); + /* Compute MTU parameters (postpone if we pull options) */ + if (!c->options.pull) + { + crypto_adjust_frame_parameters(&c->c2.frame, &c->c1.ks.key_type, + options->ciphername_defined, options->use_iv, options->replay, + packet_id_long_form); + } tls_adjust_frame_parameters (&c->c2.frame); /* Set all command-line TLS-related options */ @@ -2318,6 +2328,7 @@ do_init_crypto_tls (struct context *c, const unsigned int flags) to.renegotiate_packets = options->renegotiate_packets; to.renegotiate_seconds = options->renegotiate_seconds; to.single_session = options->single_session; + to.pull = options->pull; #ifdef ENABLE_PUSH_PEER_INFO if (options->push_peer_info) /* all there is */ to.push_peer_info_detail = 2; @@ -2686,25 +2697,26 @@ do_init_frame_tls (struct context *c) } struct context_buffers * -init_context_buffers (const struct frame *frame) +init_context_buffers (const struct frame *frame, size_t extra_bytes) { struct context_buffers *b; + const size_t buf_size = BUF_SIZE (frame) + extra_bytes; ALLOC_OBJ_CLEAR (b, struct context_buffers); - b->read_link_buf = alloc_buf (BUF_SIZE (frame)); - b->read_tun_buf = alloc_buf (BUF_SIZE (frame)); + b->read_link_buf = alloc_buf (buf_size); + b->read_tun_buf = alloc_buf (buf_size); - b->aux_buf = alloc_buf (BUF_SIZE (frame)); + b->aux_buf = alloc_buf (buf_size); #ifdef ENABLE_CRYPTO - b->encrypt_buf = alloc_buf (BUF_SIZE (frame)); - b->decrypt_buf = alloc_buf (BUF_SIZE (frame)); + b->encrypt_buf = alloc_buf (buf_size); + b->decrypt_buf = alloc_buf (buf_size); #endif #ifdef USE_COMP - b->compress_buf = alloc_buf (BUF_SIZE (frame)); - b->decompress_buf = alloc_buf (BUF_SIZE (frame)); + b->compress_buf = alloc_buf (buf_size); + b->decompress_buf = alloc_buf (buf_size); #endif return b; @@ -2740,7 +2752,16 @@ free_context_buffers (struct context_buffers *b) static void do_init_buffers (struct context *c) { - c->c2.buffers = init_context_buffers (&c->c2.frame); + size_t extra_bytes = 0; +#ifdef ENABLE_CRYPTO + if (c->options.pull) + { + /* If we use --pull, we don't know the crypto overhead yet. */ + msg (M_WARN, "*** Account for max crypto overhead ***"); + extra_bytes = crypto_max_overhead(); + } +#endif + c->c2.buffers = init_context_buffers (&c->c2.frame, extra_bytes); c->c2.buffers_owned = true; } diff --git a/src/openvpn/init.h b/src/openvpn/init.h index a819bd2..73bcb74 100644 --- a/src/openvpn/init.h +++ b/src/openvpn/init.h @@ -106,7 +106,8 @@ void inherit_context_top (struct context *dest, void close_context (struct context *c, int sig, unsigned int flags); -struct context_buffers *init_context_buffers (const struct frame *frame); +struct context_buffers *init_context_buffers (const struct frame *frame, + size_t extra_bytes); void free_context_buffers (struct context_buffers *b); diff --git a/src/openvpn/misc.h b/src/openvpn/misc.h index 65a6e55..82d1d48 100644 --- a/src/openvpn/misc.h +++ b/src/openvpn/misc.h @@ -102,6 +102,13 @@ openvpn_run_script (const struct argv *a, const struct env_set *es, const unsign return openvpn_execve_check(a, es, flags | S_SCRIPT, msg); } +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif #ifdef HAVE_STRERROR /* a thread-safe version of strerror */ diff --git a/src/openvpn/mtu.c b/src/openvpn/mtu.c index 24531c9..64d1cf3 100644 --- a/src/openvpn/mtu.c +++ b/src/openvpn/mtu.c @@ -78,8 +78,6 @@ frame_finalize (struct frame *frame, } frame->link_mtu_dynamic = frame->link_mtu; - - frame->extra_buffer += PAYLOAD_ALIGN; } /* diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index ba7f2c0..117f768 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -2862,7 +2862,7 @@ void multi_top_init (struct multi_context *m, const struct context *top) { inherit_context_top (&m->top, top); - m->top.c2.buffers = init_context_buffers (&top->c2.frame); + m->top.c2.buffers = init_context_buffers (&top->c2.frame, 0); } void diff --git a/src/openvpn/route.c b/src/openvpn/route.c index a90195f..578a708 100644 --- a/src/openvpn/route.c +++ b/src/openvpn/route.c @@ -3265,8 +3265,6 @@ struct rtmsg { #define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len)) #endif -#define max(a,b) ((a) > (b) ? (a) : (b)) - void get_default_gateway (struct route_gateway_info *rgi) { @@ -3433,7 +3431,7 @@ get_default_gateway (struct route_gateway_info *rgi) #if defined(TARGET_SOLARIS) const size_t len = sizeof(ifr->ifr_name) + sizeof(ifr->ifr_addr); #else - const size_t len = sizeof(ifr->ifr_name) + max(sizeof(ifr->ifr_addr), ifr->ifr_addr.sa_len); + const size_t len = sizeof(ifr->ifr_name) + MAX(sizeof(ifr->ifr_addr), ifr->ifr_addr.sa_len); #endif if (!ifr->ifr_addr.sa_family) diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 4291314..8a87db2 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -767,10 +767,6 @@ key_state_init (struct tls_session *session, struct key_state *ks) ks->state = S_INITIAL; ks->key_id = session->key_id; - /* - * key_id increments to KEY_ID_MASK then recycles back to 1. - * This way you know that if key_id is 0, it is the first key. - */ ++session->key_id; session->key_id &= P_KEY_ID_MASK; if (!session->key_id) @@ -1613,6 +1609,7 @@ generate_key_expansion (struct key_ctx_bi *key, key_ctx_update_implicit_iv (&key->decrypt, key2.keys[1-(int)server].hmac, MAX_HMAC_KEY_LENGTH); + key->initialized = true; ret = true; exit: @@ -1639,6 +1636,48 @@ key_ctx_update_implicit_iv(struct key_ctx *ctx, uint8_t *key, size_t key_len) { } } +bool +tls_session_update_crypto_params(struct tls_session *session, + const struct options *options, struct frame *frame) +{ + bool ret = false; + struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */ + + ASSERT (!session->opt->server); + ASSERT (ks->authenticated); + + init_key_type (&session->opt->key_type, options->ciphername, + options->ciphername_defined, options->authname, options->authname_defined, + options->keysize, true, true); + + bool packet_id_long_form = cipher_kt_mode_ofb_cfb (session->opt->key_type.cipher); + session->opt->crypto_flags_and &= ~(CO_PACKET_ID_LONG_FORM); + if (packet_id_long_form) + session->opt->crypto_flags_and = CO_PACKET_ID_LONG_FORM; + + crypto_adjust_frame_parameters (frame, &session->opt->key_type, + options->ciphername_defined, options->use_iv, options->replay, + packet_id_long_form); + frame_finalize(frame, options->ce.link_mtu_defined, options->ce.link_mtu, + options->ce.tun_mtu_defined, options->ce.tun_mtu); + frame_print (frame, D_MTU_INFO, "Data Channel MTU parms"); + + if (!generate_key_expansion (&ks->crypto_options.key_ctx_bi, + &session->opt->key_type, + ks->key_src, + &session->session_id, + &ks->session_id_remote, + false)) + { + msg (D_TLS_ERRORS, "TLS Error: server generate_key_expansion failed"); + goto cleanup; + } + ret = true; +cleanup: + CLEAR (*ks->key_src); + return ret; +} + static bool random_bytes_to_buf (struct buffer *buf, uint8_t *out, @@ -1885,6 +1924,9 @@ push_peer_info(struct buffer *buf, struct tls_session *session) /* support for P_DATA_V2 */ buf_printf(&out, "IV_PROTO=2\n"); + /* support for Negotiable Crypto Paramters */ + buf_printf(&out, "IV_NCP=2\n"); + /* push compression status */ #ifdef USE_COMP comp_generate_peer_info_string(&session->opt->comp_options, &out); @@ -2209,9 +2251,11 @@ key_method_2_read (struct buffer *buf, struct tls_multi *multi, struct tls_sessi } /* - * Generate tunnel keys if client + * Generate tunnel keys if we're a client. + * If --pull is enabled, the first key generation is postponed until after the + * pull/push, so we can process pushed cipher directives. */ - if (!session->opt->server) + if (!session->opt->server && (!session->opt->pull || ks->key_id > 0)) { if (!generate_key_expansion (&ks->crypto_options.key_ctx_bi, &session->opt->key_type, @@ -2223,7 +2267,7 @@ key_method_2_read (struct buffer *buf, struct tls_multi *multi, struct tls_sessi msg (D_TLS_ERRORS, "TLS Error: client generate_key_expansion failed"); goto error; } - + CLEAR (*ks->key_src); } @@ -2889,6 +2933,14 @@ tls_pre_decrypt (struct tls_multi *multi, #endif && (floated || link_socket_actual_match (from, &ks->remote_addr))) { + if (!ks->crypto_options.key_ctx_bi.initialized) + { + msg (D_TLS_DEBUG_LOW, + "Key %s [%d] not initialized (yet), dropping packet.", + print_link_socket_actual (from, &gc), key_id); + goto error; + } + /* return appropriate data channel decrypt key in opt */ *opt = &ks->crypto_options; if (op == P_DATA_V2) @@ -3426,6 +3478,7 @@ tls_pre_encrypt (struct tls_multi *multi, struct key_state *ks = multi->key_scan[i]; if (ks->state >= S_ACTIVE && ks->authenticated + && ks->crypto_options.key_ctx_bi.initialized #ifdef ENABLE_DEF_AUTH && !ks->auth_deferred #endif diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h index d9ff8d0..416f426 100644 --- a/src/openvpn/ssl.h +++ b/src/openvpn/ssl.h @@ -475,6 +475,20 @@ bool tls_rec_payload (struct tls_multi *multi, void tls_update_remote_addr (struct tls_multi *multi, const struct link_socket_actual *addr); +/** + * Update TLS session crypto parameters (cipher and auth) and derive data + * channel keys based on the supplied options. + * + * @param session The TLS session to update. + * @param options The options to use when updating session. + * @param frame The frame options for this session (frame overhead is + * adjusted based on the selected cipher/auth). + * + * @return true if updating succeeded, false otherwise. + */ +bool tls_session_update_crypto_params(struct tls_session *session, + const struct options *options, struct frame *frame); + #ifdef MANAGEMENT_DEF_AUTH static inline char * tls_get_peer_info(const struct tls_multi *multi) diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index a0df0ff..9183dab 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -149,7 +149,12 @@ struct key_source2 { struct key_state { int state; - int key_id; /* inherited from struct tls_session below */ + + /** + * Key id for this key_state, inherited from struct tls_session. + * @see tls_session::key_id. + */ + int key_id; struct key_state_ssl ks_ssl; /* contains SSL object and BIOs for the control channel */ @@ -231,6 +236,7 @@ struct tls_options #ifdef ENABLE_OCC bool disable_occ; #endif + bool pull; #ifdef ENABLE_PUSH_PEER_INFO int push_peer_info_detail; #endif @@ -367,7 +373,13 @@ struct tls_session int initial_opcode; /* our initial P_ opcode */ struct session_id session_id; /* our random session ID */ - int key_id; /* increments with each soft reset (for key renegotiation) */ + + /** + * The current active key id, used to keep track of renegotiations. + * key_id increments with each soft reset to KEY_ID_MASK then recycles back + * to 1. This way you know that if key_id is 0, it is the first key. + */ + int key_id; int limit_next; /* used for traffic shaping on the control channel */ -- 2.7.4