From: Michael Rash Date: Sun, 8 Jul 2012 12:36:30 +0000 (-0400) Subject: Only cache replay digests for SPA packets that decrypt X-Git-Tag: fwknop-2.0.1-pre1~5 X-Git-Url: http://www.cipherdyne.com/cgi-bin/gitweb.cgi?p=fwknop.git;a=commitdiff_plain;h=be4193d734850fe60f14a26b547525ea0b9ce1e9 Only cache replay digests for SPA packets that decrypt This change ensures that we only cache replay digests for those SPA packets that actually decrypt. Not doing this would have allowed an attacker to potentially fill up digest cache space with digests for garbage packets. --- diff --git a/server/incoming_spa.c b/server/incoming_spa.c index 024efe3..2c33192 100644 --- a/server/incoming_spa.c +++ b/server/incoming_spa.c @@ -110,6 +110,64 @@ preprocess_spa_data(fko_srv_options_t *opts, const char *src_ip) return(FKO_SUCCESS); } +/* For replay attack detection +*/ +static int +get_raw_digest(char **digest, char *pkt_data) +{ + fko_ctx_t ctx = NULL; + char *tmp_digest = NULL; + int res = FKO_SUCCESS; + + /* initialize an FKO context with no decryption key just so + * we can get the outer message digest + */ + res = fko_new_with_data(&ctx, (char *)pkt_data, NULL); + if(res != FKO_SUCCESS) + { + log_msg(LOG_WARNING, "Error initializing FKO context from SPA data: %s", + fko_errstr(res)); + fko_destroy(ctx); + return(SPA_MSG_FKO_CTX_ERROR); + } + + res = fko_set_raw_spa_digest_type(ctx, FKO_DEFAULT_DIGEST); + if(res != FKO_SUCCESS) + { + log_msg(LOG_WARNING, "Error setting digest type for SPA data: %s", + fko_errstr(res)); + fko_destroy(ctx); + return(SPA_MSG_DIGEST_ERROR); + } + + res = fko_set_raw_spa_digest(ctx); + if(res != FKO_SUCCESS) + { + log_msg(LOG_WARNING, "Error setting digest for SPA data: %s", + fko_errstr(res)); + fko_destroy(ctx); + return(SPA_MSG_DIGEST_ERROR); + } + + res = fko_get_raw_spa_digest(ctx, &tmp_digest); + if(res != FKO_SUCCESS) + { + log_msg(LOG_WARNING, "Error getting digest from SPA data: %s", + fko_errstr(res)); + fko_destroy(ctx); + return(SPA_MSG_DIGEST_ERROR); + } + + *digest = strdup(tmp_digest); + + if (digest == NULL) + return SPA_MSG_ERROR; + + fko_destroy(ctx); + return res; +} + + /* Popluate a spa_data struct from an initialized (and populated) FKO context. */ static int @@ -178,9 +236,10 @@ incoming_spa(fko_srv_options_t *opts) */ fko_ctx_t ctx = NULL; - char *spa_ip_demark, *gpg_id; + char *spa_ip_demark, *gpg_id, *raw_digest = NULL; time_t now_ts; - int res, status, ts_diff, enc_type, found_acc_sip=0, stanza_num=0; + int res, status, ts_diff, enc_type, stanza_num=0; + int added_replay_digest = 0; spa_pkt_info_t *spa_pkt = &(opts->spa_pkt); @@ -213,7 +272,17 @@ incoming_spa(fko_srv_options_t *opts) if(strncasecmp(opts->config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0) /* Check for a replay attack */ - if (is_replay(opts, spa_pkt->packet_data) != SPA_MSG_SUCCESS) + res = get_raw_digest(&raw_digest, (char *)spa_pkt->packet_data); + if(res != FKO_SUCCESS) + { + if (raw_digest != NULL) + free(raw_digest); + return; + } + if (raw_digest == NULL) + return; + + if (is_replay(opts, raw_digest) != SPA_MSG_SUCCESS) return; } else @@ -240,8 +309,6 @@ incoming_spa(fko_srv_options_t *opts) continue; } - found_acc_sip = 1; - log_msg(LOG_INFO, "(stanza #%d) SPA Packet from IP: %s received with access source match", stanza_num, spadat.pkt_source_ip); @@ -366,7 +433,7 @@ incoming_spa(fko_srv_options_t *opts) continue; } - /* Do we have a valid FKO context? + /* Do we have a valid FKO context? Did the SPA decrypt properly? */ if(res != FKO_SUCCESS) { @@ -383,6 +450,23 @@ incoming_spa(fko_srv_options_t *opts) continue; } + /* Add this SPA packet into the replay detection cache + */ + if (! added_replay_digest) + { + res = add_replay(opts, raw_digest); + if (res != SPA_MSG_SUCCESS) + { + log_msg(LOG_WARNING, "(stanza #%d) Could not add digest to replay cache", + stanza_num); + if(ctx != NULL) + fko_destroy(ctx); + acc = acc->next; + continue; + } + added_replay_digest = 1; + } + /* At this point, we assume the SPA data is valid. Now we need to see * if it meets our access criteria. */ @@ -656,12 +740,8 @@ incoming_spa(fko_srv_options_t *opts) break; } - if(! found_acc_sip) - { - log_msg(LOG_WARNING, - "No access data found for source IP: %s", spadat.pkt_source_ip - ); - } + if (raw_digest != NULL) + free(raw_digest); return; } diff --git a/server/replay_cache.c b/server/replay_cache.c index fdc99e5..2b164e9 100644 --- a/server/replay_cache.c +++ b/server/replay_cache.c @@ -77,61 +77,6 @@ #define DATE_LEN 18 #define MAX_DIGEST_SIZE 64 -static int -get_raw_digest(char **digest, char *pkt_data) -{ - fko_ctx_t ctx = NULL; - char *tmp_digest = NULL; - int res = FKO_SUCCESS; - - /* initialize an FKO context with no decryption key just so - * we can get the outer message digest - */ - res = fko_new_with_data(&ctx, (char *)pkt_data, NULL); - if(res != FKO_SUCCESS) - { - log_msg(LOG_WARNING, "Error initializing FKO context from SPA data: %s", - fko_errstr(res)); - fko_destroy(ctx); - return(SPA_MSG_FKO_CTX_ERROR); - } - - res = fko_set_raw_spa_digest_type(ctx, FKO_DEFAULT_DIGEST); - if(res != FKO_SUCCESS) - { - log_msg(LOG_WARNING, "Error setting digest type for SPA data: %s", - fko_errstr(res)); - fko_destroy(ctx); - return(SPA_MSG_DIGEST_ERROR); - } - - res = fko_set_raw_spa_digest(ctx); - if(res != FKO_SUCCESS) - { - log_msg(LOG_WARNING, "Error setting digest for SPA data: %s", - fko_errstr(res)); - fko_destroy(ctx); - return(SPA_MSG_DIGEST_ERROR); - } - - res = fko_get_raw_spa_digest(ctx, &tmp_digest); - if(res != FKO_SUCCESS) - { - log_msg(LOG_WARNING, "Error getting digest from SPA data: %s", - fko_errstr(res)); - fko_destroy(ctx); - return(SPA_MSG_DIGEST_ERROR); - } - - *digest = strdup(tmp_digest); - - if (digest == NULL) - return SPA_MSG_ERROR; - - fko_destroy(ctx); - return res; -} - /* Rotate the digest file by simply renaming it. */ static void @@ -475,47 +420,45 @@ replay_db_cache_init(fko_srv_options_t *opts) #endif /* USE_FILE_CACHE */ /* Take an fko context, pull the digest and use it as the key to check the - * replay db (digest cache). Returns 1 if there was a match (a replay), - * 0 for no match, and -1 on error. + * replay db (digest cache). */ int -is_replay(fko_srv_options_t *opts, unsigned char *pkt_data) +is_replay(fko_srv_options_t *opts, char *digest) { #ifdef NO_DIGEST_CACHE return(-1); #else #if USE_FILE_CACHE - return is_replay_file_cache(opts, pkt_data); + return is_replay_file_cache(opts, digest); #else - return is_replay_dbm_cache(opts, pkt_data); + return is_replay_dbm_cache(opts, digest); #endif #endif /* NO_DIGEST_CACHE */ } -#if USE_FILE_CACHE int -is_replay_file_cache(fko_srv_options_t *opts, unsigned char *pkt_data) +add_replay(fko_srv_options_t *opts, char *digest) { - char *digest = NULL; - char src_ip[INET_ADDRSTRLEN+1] = {0}; - char dst_ip[INET_ADDRSTRLEN+1] = {0}; - int res = 0, digest_len = 0; - FILE *digest_file_ptr = NULL; - - struct digest_cache_list *digest_list_ptr = NULL, *digest_elm = NULL; +#ifdef NO_DIGEST_CACHE + return(-1); +#else - res = get_raw_digest(&digest, (char *)pkt_data); +#if USE_FILE_CACHE + return add_replay_file_cache(opts, digest); +#else + return add_replay_dbm_cache(opts, digest); +#endif +#endif /* NO_DIGEST_CACHE */ +} - if(res != FKO_SUCCESS) - { - if (digest != NULL) - free(digest); - return res; - } +#if USE_FILE_CACHE +int +is_replay_file_cache(fko_srv_options_t *opts, char *digest) +{ + int digest_len = 0; - if (digest == NULL) - return SPA_MSG_ERROR; + struct digest_cache_list *digest_list_ptr = NULL; digest_len = strlen(digest); @@ -529,21 +472,29 @@ is_replay_file_cache(fko_srv_options_t *opts, unsigned char *pkt_data) replay_warning(opts, &(digest_list_ptr->cache_info)); - free(digest); return(SPA_MSG_REPLAY); } } + return(SPA_MSG_SUCCESS); +} + +int +add_replay_file_cache(fko_srv_options_t *opts, char *digest) +{ + FILE *digest_file_ptr = NULL; + int digest_len = 0; + char src_ip[INET_ADDRSTRLEN+1] = {0}; + char dst_ip[INET_ADDRSTRLEN+1] = {0}; + + struct digest_cache_list *digest_elm = NULL; + + digest_len = strlen(digest); - /* If we make it here, then this is a new SPA packet that needs to be - * added to the cache. We've already decrypted the data, so we know that - * the contents are valid. - */ if ((digest_elm = calloc(1, sizeof(struct digest_cache_list))) == NULL) { log_msg(LOG_WARNING, "Error calloc() returned NULL for digest cache element", fko_errstr(SPA_MSG_ERROR)); - free(digest); return(SPA_MSG_ERROR); } if ((digest_elm->cache_info.digest = calloc(1, digest_len+1)) == NULL) @@ -551,7 +502,6 @@ is_replay_file_cache(fko_srv_options_t *opts, unsigned char *pkt_data) log_msg(LOG_WARNING, "Error calloc() returned NULL for digest cache string", fko_errstr(SPA_MSG_ERROR)); free(digest_elm); - free(digest); return(SPA_MSG_ERROR); } @@ -574,7 +524,6 @@ is_replay_file_cache(fko_srv_options_t *opts, unsigned char *pkt_data) { log_msg(LOG_WARNING, "Could not open digest cache: %s", opts->config[CONF_DIGEST_FILE]); - free(digest); return(SPA_MSG_DIGEST_CACHE_ERROR); } @@ -593,14 +542,13 @@ is_replay_file_cache(fko_srv_options_t *opts, unsigned char *pkt_data) fclose(digest_file_ptr); - free(digest); return(SPA_MSG_SUCCESS); } #endif /* USE_FILE_CACHE */ #if !USE_FILE_CACHE int -is_replay_dbm_cache(fko_srv_options_t *opts, unsigned char *pkt_data) +is_replay_dbm_cache(fko_srv_options_t *opts, char *digest) { #ifdef NO_DIGEST_CACHE return 0; @@ -614,22 +562,10 @@ is_replay_dbm_cache(fko_srv_options_t *opts, unsigned char *pkt_data) datum db_key, db_ent; char *digest = NULL; - int digest_len, res; + int digest_len, res = SPA_MSG_SUCCESS; digest_cache_info_t dc_info; - res = get_raw_digest(&digest, (char *)pkt_data); - - if(res != FKO_SUCCESS) - { - if (digest != NULL) - free(digest); - return res; - } - - if (digest == NULL) - return SPA_MSG_ERROR; - digest_len = strlen(digest); db_key.dptr = digest; @@ -652,7 +588,6 @@ is_replay_dbm_cache(fko_srv_options_t *opts, unsigned char *pkt_data) MY_DBM_STRERROR(errno) ); - free(digest); return(SPA_MSG_DIGEST_CACHE_ERROR); } @@ -676,9 +611,65 @@ is_replay_dbm_cache(fko_srv_options_t *opts, unsigned char *pkt_data) #ifdef HAVE_LIBGDBM free(db_ent.dptr); #endif - res = SPA_MSG_REPLAY; - } else { + } + + MY_DBM_CLOSE(rpdb); + + return(res); +#endif /* NO_DIGEST_CACHE */ +} + +int +add_replay_dbm_cache(fko_srv_options_t *opts, char *digest) +{ +#ifdef NO_DIGEST_CACHE + return 0; +#else + +#ifdef HAVE_LIBGDBM + GDBM_FILE rpdb; +#elif HAVE_LIBNDBM + DBM *rpdb; +#endif + datum db_key, db_ent; + + char *digest = NULL; + int digest_len, res = SPA_MSG_SUCCESS; + + digest_cache_info_t dc_info; + + digest_len = strlen(digest); + + db_key.dptr = digest; + db_key.dsize = digest_len; + + /* Check the db for the key + */ +#ifdef HAVE_LIBGDBM + rpdb = gdbm_open( + opts->config[CONF_DIGEST_DB_FILE], 512, GDBM_WRCREAT, S_IRUSR|S_IWUSR, 0 + ); +#elif HAVE_LIBNDBM + rpdb = dbm_open(opts->config[CONF_DIGEST_DB_FILE], O_RDWR, 0); +#endif + + if(!rpdb) + { + log_msg(LOG_WARNING, "Error opening digest_cache: '%s': %s", + opts->config[CONF_DIGEST_DB_FILE], + MY_DBM_STRERROR(errno) + ); + + return(SPA_MSG_DIGEST_CACHE_ERROR); + } + + db_ent = MY_DBM_FETCH(rpdb, db_key); + + /* If the datum is null, we have a new entry. + */ + if(db_ent.dptr == NULL) + { /* This is a new SPA packet that needs to be added to the cache. */ dc_info.src_ip = opts->spa_pkt.packet_src_ip; @@ -703,13 +694,14 @@ is_replay_dbm_cache(fko_srv_options_t *opts, unsigned char *pkt_data) res = SPA_MSG_SUCCESS; } + else + res = SPA_MSG_DIGEST_CACHE_ERROR; MY_DBM_CLOSE(rpdb); - free(digest); return(res); #endif /* NO_DIGEST_CACHE */ -} + #endif /* USE_FILE_CACHE */ #if USE_FILE_CACHE diff --git a/server/replay_cache.h b/server/replay_cache.h index 8d785b7..d523fa1 100644 --- a/server/replay_cache.h +++ b/server/replay_cache.h @@ -59,14 +59,17 @@ struct digest_cache_list { /* Prototypes */ int replay_cache_init(fko_srv_options_t *opts); -int is_replay(fko_srv_options_t *opts, unsigned char *pkt_data); +int is_replay(fko_srv_options_t *opts, char *digest); +int add_replay(fko_srv_options_t *opts, char *digest); #ifdef USE_FILE_CACHE int replay_file_cache_init(fko_srv_options_t *opts); -int is_replay_file_cache(fko_srv_options_t *opts, unsigned char *pkt_data); +int is_replay_file_cache(fko_srv_options_t *opts, char *digest); +int add_replay_file_cache(fko_srv_options_t *opts, char *digest); void free_replay_list(fko_srv_options_t *opts); #else int replay_db_cache_init(fko_srv_options_t *opts); -int is_replay_dbm_cache(fko_srv_options_t *opts, unsigned char *pkt_data); +int is_replay_dbm_cache(fko_srv_options_t *opts, char *digest); +int add_replay_dbm_cache(fko_srv_options_t *opts, char *digest); #endif #endif /* REPLAY_CACHE_H */